diff --git a/README.md b/README.md index fcf08eeac..db7b5d688 100644 --- a/README.md +++ b/README.md @@ -42,27 +42,61 @@ ## Quickstart (CLI) +To install: + +```sh +# With --dryrun 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 -- --dryrun +# If things look good, install for real. +curl -fsSL https://d2lang.com/install.sh | sh -s -- +``` + The most convenient way to use D2 is to just run it as a CLI executable to produce SVGs from `.d2` files. ```sh -go install oss.terrastruct.com/d2 - echo 'x -> y -> z' > in.d2 d2 --watch in.d2 out.svg ``` A browser window will open with `out.svg` and live-reload on changes to `in.d2`. -### MacOS +### Installing from source -Homebrew package coming soon. +```sh +go install oss.terrastruct.com/d2 +``` -### Linux/Windows +### Install We have precompiled binaries on the [releases](https://github.com/terrastruct/d2/releases) -page. D2 will be added to OS-respective package managers soon. +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 +For now, if you don't want to install from source, just use our install script: +Pass `--tala` if you want to install our improved but closed source layout engine +tala. See the docs on [layout engine](#layout-engine) below. + +```sh +# With --dryrun 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 -- --dryrun +# If things look good, install for real. +curl -fsSL https://d2lang.com/install.sh | sh -s -- +``` + +To uninstall: + +```sh +curl -fsSL https://d2lang.com/install.sh | sh -s -- --uninstall --dryrun +# If things look good, install for real. +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 +> dependency on v8go for executing dagre. ## Quickstart (library) @@ -156,6 +190,14 @@ D2 intends to integrate with a variety of layout engines, e.g. `dot`, as well as single-purpose layout types like sequence diagrams. You can choose whichever layout engine you like and works best for the diagram you're making. +You can just pass `--tala` to the install script to install tala as well: + +``` +curl -fsSL https://d2lang.com/install.sh | sh -s -- --tala --dryrun +# If things look good, install for real. +curl -fsSL https://d2lang.com/install.sh | sh -s -- --tala +``` + ## Comparison For a comparison against other popular text-to-diagram tools, see diff --git a/ci/release/README.md b/ci/release/README.md new file mode 100644 index 000000000..4530983e4 --- /dev/null +++ b/ci/release/README.md @@ -0,0 +1,49 @@ +# release + +## _install.sh + +The template for the install script in the root of the repository. + +### gen_install.sh + +Generates the install.sh script in the root of the repository by prepending the libraries +it depends on from ../sub/lib. + +## release.sh + +- ./release.sh is the top level script to generate a new release. + Run with --help for usage. + +## build.sh + +- ./build.sh builds the release archives for each platform into ./build//*.tar.gz + Run with --help for usage. + +> note: Remember for production releases you need to set the $TSTRUCT_OS_ARCH_BUILDER +> variables as we must compile d2 directly on each release target to include dagre. +> See https://github.com/terrastruct/d2/issues/31 + +Use `--host-only` to build only the release for the host's OS-ARCH pair. + +### build_docker.sh + +Helper script called by build.sh to build D2 on each linux runner inside Docker. +The Dockerfile is in ./builders/Dockerfile + +### _build.sh + +Called by build.sh (with --local or macOS) or build_docker.sh (on linux) to create the +release archive. + +Do not invoke directly. If you want to produce a build for a single platform run build.sh +as so: + +```sh + # To only build the linux-amd64 release. +./build.sh --run=linux-amd64 +``` + +```sh + # To only build the linux-amd64 release locally. +./build.sh --local --run=linux-amd64 +``` diff --git a/ci/release/_build.sh b/ci/release/_build.sh new file mode 100755 index 000000000..c5170219a --- /dev/null +++ b/ci/release/_build.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu +cd -- "$(dirname "$0")/../.." +. ./ci/sub/lib.sh + +sh_c mkdir -p "$HW_BUILD_DIR" +sh_c rsync --recursive --perms --delete \ + --human-readable --copy-links ./ci/release/template/ "$HW_BUILD_DIR/" +VERSION=$VERSION sh_c eval "'$HW_BUILD_DIR/README.md.sh'" \> "'$HW_BUILD_DIR/README.md'" +sh_c rm -f "$HW_BUILD_DIR/README.md.sh" +sh_c find "$HW_BUILD_DIR" -exec touch {} \\\; + +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 + +ARCHIVE=$PWD/$ARCHIVE +cd "$(dirname "$HW_BUILD_DIR")" +sh_c tar -czf "$ARCHIVE" "$(basename "$HW_BUILD_DIR")" +cd ->/dev/null diff --git a/ci/release/_install.sh b/ci/release/_install.sh new file mode 100755 index 000000000..c266341cc --- /dev/null +++ b/ci/release/_install.sh @@ -0,0 +1,401 @@ +#!/bin/sh +set -eu + +cd -- "$(dirname "$0")/../sub/lib" +. ./log.sh +. ./flag.sh +. ./release.sh +cd - >/dev/null + +help() { + arg0="$0" + if [ "$0" = sh ]; then + arg0="curl -fsSL https://d2lang.com/install.sh | sh -s --" + fi + + cat < but the release archive in + ~/.cache/d2/release will remain. + +--uninstall: + Uninstall the installed version of d2. The --method and --prefix flags must be the same + 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 + package manager to uninstall instead. + +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- + +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. +EOF +} + +main() { + METHOD=standalone + while :; do + flag_parse "$@" + case "$FLAG" in + h|help) + help + return 0 + ;; + dry-run) + flag_noarg && shift "$FLAGSHIFT" + DRY_RUN=1 + ;; + version) + flag_nonemptyarg && shift "$FLAGSHIFT" + VERSION=$FLAGARG + ;; + tala) + shift "$FLAGSHIFT" + TALA=${FLAGARG:-latest} + ;; + edge) + flag_noarg && shift "$FLAGSHIFT" + EDGE=1 + echoerr "$FLAGRAW is currently unimplemented" + return 1 + ;; + method) + flag_nonemptyarg && shift "$FLAGSHIFT" + METHOD=$FLAGARG + echoerr "$FLAGRAW is currently unimplemented" + return 1 + ;; + prefix) + flag_nonemptyarg && shift "$FLAGSHIFT" + export PREFIX=$FLAGARG + ;; + force) + flag_noarg && shift "$FLAGSHIFT" + FORCE=1 + ;; + uninstall) + flag_noarg && shift "$FLAGSHIFT" + UNINSTALL=1 + ;; + '') + shift "$FLAGSHIFT" + break + ;; + *) + flag_errusage "unrecognized flag $FLAGRAW" + ;; + esac + done + + if [ $# -gt 0 ]; then + flag_errusage "no arguments are accepted" + fi + + REPO=${REPO:-terrastruct/d2} + OS=$(os) + ARCH=$(arch) + if [ -z "${PREFIX-}" -a "$OS" = macos -a "$ARCH" = arm64 ]; then + # M1 Mac's do not allow modifications to /usr/local even with sudo. + PREFIX=$HOME/.local + fi + PREFIX=${PREFIX:-/usr/local} + CACHE_DIR=$(cache_dir) + mkdir -p "$CACHE_DIR" + INSTALL_DIR=$PREFIX/lib/d2 + + if [ -n "${UNINSTALL-}" ]; then + uninstall + return 0 + fi + + VERSION=${VERSION:-latest} + if [ "$VERSION" = latest ]; then + header "fetching latest release info" + fetch_release_info + fi + + install +} + +install() { + install_d2 + if [ -n "${TALA-}" ]; then + # Run in subshell to avoid overwriting VERSION. + TALA_VERSION="$( install_tala && echo "$VERSION" )" + fi + + COLOR=2 header success + log "d2-$VERSION-$OS-$ARCH has been successfully installed into $PREFIX" + if [ -n "${TALA-}" ]; then + log "tala-$TALA_VERSION-$OS-$ARCH has been successfully installed into $PREFIX" + fi + if ! echo "$PATH" | grep -qF "$PREFIX/bin"; then + logcat >&2 <&2 </dev/null; then + INSTALLED_VERSION="$(d2 version)" + if [ ! "${FORCE-}" -a "$VERSION" = "$INSTALLED_VERSION" ]; then + log "skipping installation as version $VERSION is already installed." + return 0 + fi + log "uninstalling $INSTALLED_VERSION to install $VERSION" + if ! uninstall_d2; then + warn "failed to uninstall $INSTALLED_VERSION" + fi + fi + + header "installing d2-$VERSION" + install_standalone_d2 +} + +install_standalone_d2() { + ARCHIVE="d2-$VERSION-$OS-$ARCH.tar.gz" + 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_url=$(sh_c 'sed -n $((asset_line-3))p "$RELEASE_INFO" | sed "s/^.*: \"\(.*\)\",$/\1/g"') + fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream' + + sh_c="sh_c" + if ! is_prefix_writable; then + sh_c="sudo_sh_c" + fi + + "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" + "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$VERSION\" && make install PREFIX=\"$PREFIX\"'" +} + +install_tala() { + REPO="${REPO_TALA:-terrastruct/TALA}" + VERSION=$TALA + RELEASE_INFO= + fetch_release_info + header "installing tala-$VERSION" + install_standalone_tala +} + +install_standalone_tala() { + ARCHIVE="tala-$VERSION-$OS-$ARCH.tar.gz" + log "installing standalone release $ARCHIVE from github" + + 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"') + + fetch_gh "$asset_url" "$CACHE_DIR/$ARCHIVE" 'application/octet-stream' + + sh_c="sh_c" + if ! is_prefix_writable; then + sh_c="sudo_sh_c" + fi + + "$sh_c" tar -C "$INSTALL_DIR" -xzf "$CACHE_DIR/$ARCHIVE" + "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$VERSION\" && make install PREFIX=\"$PREFIX\"'" +} + +uninstall() { + if ! command -v d2 >/dev/null; then + warn "no version of d2 installed" + return 0 + fi + 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" + uninstall_standalone_d2 +} + +uninstall_standalone_d2() { + log "uninstalling standalone release of d2-$INSTALLED_VERSION" + + if [ ! -e "$INSTALL_DIR/d2-$INSTALLED_VERSION" ]; then + warn "missing standalone install release directory $INSTALL_DIR/d2-$INSTALLED_VERSION" + warn "d2 have been installed via some other installation method." + return 1 + fi + + sh_c="sh_c" + if ! is_prefix_writable; then + sh_c="sudo_sh_c" + fi + + "$sh_c" sh -c "'cd \"$INSTALL_DIR/d2-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'" + "$sh_c" rm -rf "$INSTALL_DIR/d2-$INSTALLED_VERSION" +} + +uninstall_tala() { + header "uninstalling tala-$INSTALLED_VERSION" + uninstall_standalone_tala +} + +uninstall_standalone_tala() { + log "uninstalling standalone release tala-$INSTALLED_VERSION" + + if [ ! -e "$INSTALL_DIR/tala-$INSTALLED_VERSION" ]; then + warn "missing standalone install release directory $INSTALL_DIR/tala-$INSTALLED_VERSION" + warn "tala have been installed via some other installation method." + return 1 + fi + + sh_c="sh_c" + if ! is_prefix_writable; then + sh_c="sudo_sh_c" + fi + + "$sh_c" sh -c "'cd \"$INSTALL_DIR/tala-$INSTALLED_VERSION\" && make uninstall PREFIX=\"$PREFIX\"'" + "$sh_c" rm -rf "$INSTALL_DIR/tala-$INSTALLED_VERSION" +} + +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 +} + +cache_dir() { + if [ -n "${XDG_CACHE_HOME-}" ]; then + echo "$XDG_CACHE_HOME/d2/release" + elif [ -n "${HOME-}" ]; then + echo "$HOME/.cache/d2/release" + else + echo "/tmp/d2-cache/release" + fi +} + +fetch_release_info() { + if [ -n "${RELEASE_INFO-}" ]; then + return 0 + fi + + log "fetching info on $VERSION version of $REPO" + RELEASE_INFO=$(mktemp -d)/release-info.json + if [ "$VERSION" = latest ]; then + release_info_url="https://api.github.com/repos/$REPO/releases/$VERSION" + else + release_info_url="https://api.github.com/repos/$REPO/releases/tags/$VERSION" + fi + fetch_gh "$release_info_url" "$RELEASE_INFO" \ + 'application/json' + VERSION=$(cat "$RELEASE_INFO" | grep -m1 tag_name | sed 's/^.*: "\(.*\)",$/\1/g') +} + +curl_gh() { + sh_c curl -fL ${GITHUB_TOKEN+"-H \"Authorization: Bearer \$GITHUB_TOKEN\""} "$@" +} + +fetch_gh() { + url=$1 + file=$2 + accept=$3 + + if [ -e "$file" ]; then + log "reusing $file" + return + fi + + curl_gh -#o "$file.inprogress" -C- -H "'Accept: $accept'" "$url" + sh_c mv "$file.inprogress" "$file" +} + +main "$@" diff --git a/ci/release/build.sh b/ci/release/build.sh new file mode 100755 index 000000000..b716a91a0 --- /dev/null +++ b/ci/release/build.sh @@ -0,0 +1,198 @@ +#!/bin/sh +set -eu +cd -- "$(dirname "$0")/../.." +. ./ci/sub/lib.sh + +help() { + cat </d2---.tar.gz + +The version is detected via git describe which will use the git tag for the current +commit if available. + +Flags: + +--rebuild + By default build.sh will avoid rebuilding finished assets if they already exist but if you + changed something and need to force rebuild, use this flag. + +--local + By default build.sh uses \$TSTRUCT_MACOS_AMD64_BUILDER, \$TSTRUCT_MACOS_ARM64_BUILDER, + \$TSTRUCT_LINUX_AMD64_BUILDER and \$TSTRUCT_LINUX_ARM64_BUILDER to build the release + archives. It's required for now due to the following issue: + https://github.com/terrastruct/d2/issues/31 With --local, build.sh will cross compile + locally. warning: This is only for testing purposes, do not use in production! + +--host-only + Use to build the release archive for the host OS-ARCH only. All logging is done to stderr + so in a script you can read from stdout to get the path to the release archive. + +--run=regex + Use to run only the OS-ARCH jobs that match the given regex. e.g. --run=linux only runs + the linux jobs. --run=linux-amd64 only runs the linux-amd64 job. + +--version vX.X.X + Use to overwrite the version detected from git. +EOF +} + +main() { + while :; do + flag_parse "$@" + case "$FLAG" in + h|help) + help + return 0 + ;; + rebuild) + flag_noarg && shift "$FLAGSHIFT" + REBUILD=1 + ;; + local) + flag_noarg && shift "$FLAGSHIFT" + LOCAL=1 + ;; + dry-run) + flag_noarg && shift "$FLAGSHIFT" + DRY_RUN=1 + ;; + run) + flag_reqarg && shift "$FLAGSHIFT" + JOBFILTER="$FLAGARG" + ;; + host-only) + flag_noarg && shift "$FLAGSHIFT" + HOST_ONLY=1 + LOCAL=1 + ;; + version) + flag_nonemptyarg && shift "$FLAGSHIFT" + VERSION=$FLAGARG + ;; + '') + shift "$FLAGSHIFT" + break + ;; + *) + flag_errusage "unrecognized flag $FLAGRAW" + ;; + esac + done + + if [ $# -gt 0 ]; then + flag_errusage "no arguments are accepted" + fi + + 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 + return 0 + fi + + runjob linux-amd64 'OS=linux ARCH=amd64 build' & + runjob linux-arm64 'OS=linux ARCH=arm64 build' & + runjob macos-amd64 'OS=macos ARCH=amd64 build' & + runjob macos-arm64 'OS=macos ARCH=arm64 build' & + waitjobs +} + +build() { + HW_BUILD_DIR="$BUILD_DIR/$OS-$ARCH/d2-$VERSION" + ARCHIVE="$BUILD_DIR/d2-$VERSION-$OS-$ARCH.tar.gz" + + if [ -e "$ARCHIVE" -a -z "${REBUILD-}" ]; then + log "skipping as already built at $ARCHIVE" + return 0 + fi + + if [ -n "${LOCAL-}" ]; then + build_local + return 0 + fi + + case $OS in + macos) + case $ARCH in + amd64) + RHOST=$TSTRUCT_MACOS_AMD64_BUILDER build_rhost_macos + ;; + arm64) + RHOST=$TSTRUCT_MACOS_ARM64_BUILDER build_rhost_macos + ;; + *) + warn "no builder for OS=$OS ARCH=$ARCH, building locally..." + build_local + ;; + esac + ;; + linux) + case $ARCH in + amd64) + RHOST=$TSTRUCT_LINUX_AMD64_BUILDER build_rhost_linux + ;; + arm64) + RHOST=$TSTRUCT_LINUX_ARM64_BUILDER build_rhost_linux + ;; + *) + warn "no builder for OS=$OS ARCH=$ARCH, building locally..." + build_local + ;; + esac + ;; + *) + warn "no builder for OS=$OS, building locally..." + build_local + ;; + esac +} + +build_local() { + export DRY_RUN \ + HW_BUILD_DIR \ + VERSION \ + OS \ + ARCH \ + ARCHIVE + sh_c ./ci/release/_build.sh +} + +build_rhost_macos() { + sh_c ssh "$RHOST" mkdir -p src + sh_c rsync --archive --human-readable --delete ./ "$RHOST:src/d2/" + sh_c ssh -tttt "$RHOST" "DRY_RUN=${DRY_RUN-} \ +HW_BUILD_DIR=$HW_BUILD_DIR \ +VERSION=$VERSION \ +OS=$OS \ +ARCH=$ARCH \ +ARCHIVE=$ARCHIVE \ +TERM=$TERM \ +PATH=\\\"/usr/local/bin:/usr/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin\\\${PATH+:\\\$PATH}\\\" \ +./src/d2/ci/release/_build.sh" + sh_c mkdir -p "$HW_BUILD_DIR" + sh_c rsync --archive --human-readable "$RHOST:src/d2/$ARCHIVE" "$ARCHIVE" +} + +build_rhost_linux() { + sh_c ssh "$RHOST" mkdir -p src + sh_c rsync --archive --human-readable --delete ./ "$RHOST:src/d2/" + sh_c ssh -tttt "$RHOST" "DRY_RUN=${DRY_RUN-} \ +HW_BUILD_DIR=$HW_BUILD_DIR \ +VERSION=$VERSION \ +OS=$OS \ +ARCH=$ARCH \ +ARCHIVE=$ARCHIVE \ +TERM=$TERM \ +./src/d2/ci/release/build_docker.sh" + sh_c mkdir -p "$HW_BUILD_DIR" + sh_c rsync --archive --human-readable "$RHOST:src/d2/$ARCHIVE" "$ARCHIVE" +} + +ssh() { + command ssh -o='StrictHostKeyChecking=accept-new' "$@" +} + +main "$@" diff --git a/ci/release/build/.gitignore b/ci/release/build/.gitignore new file mode 100644 index 000000000..72e8ffc0d --- /dev/null +++ b/ci/release/build/.gitignore @@ -0,0 +1 @@ +* diff --git a/ci/release/build_docker.sh b/ci/release/build_docker.sh new file mode 100755 index 000000000..7c3751af2 --- /dev/null +++ b/ci/release/build_docker.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu +cd -- "$(dirname "$0")/../.." +. ./ci/sub/lib.sh + +tag="$(sh_c docker build \ + --build-arg GOVERSION="1.19.3.linux-$ARCH" \ + -qf ./ci/release/builders/Dockerfile ./ci/release/builders)" +docker_run \ + -e DRY_RUN \ + -e HW_BUILD_DIR \ + -e VERSION \ + -e OS \ + -e ARCH \ + -e ARCHIVE \ + -e TERM \ + "$tag" ./src/d2/ci/release/_build.sh diff --git a/ci/release/builders/Dockerfile b/ci/release/builders/Dockerfile new file mode 100644 index 000000000..5adc0bd25 --- /dev/null +++ b/ci/release/builders/Dockerfile @@ -0,0 +1,12 @@ +FROM debian:10 + +RUN apt-get update +RUN apt-get install -y curl + +ARG GOVERSION= +RUN curl -fsSL "https://go.dev/dl/go$GOVERSION.tar.gz" >/tmp/go.tar.gz +RUN tar -C /usr/local -xzf /tmp/go.tar.gz +ENV PATH="/usr/local/go/bin:$PATH" + +RUN apt-get install -y build-essential +RUN apt-get install -y rsync diff --git a/ci/release/builders/Dockerfile-centos b/ci/release/builders/Dockerfile-centos new file mode 100644 index 000000000..66aaf6f17 --- /dev/null +++ b/ci/release/builders/Dockerfile-centos @@ -0,0 +1,16 @@ +FROM centos:7 + +ARG GOVERSION= + +RUN curl -fsSL "https://go.dev/dl/go$GOVERSION.tar.gz" >/tmp/go.tar.gz +RUN tar -C /usr/local -xzf /tmp/go.tar.gz + +ENV PATH="/usr/local/go/bin:$PATH" + +RUN yum install -y rsync wget +RUN yum groupinstall -y 'Development Tools' + +RUN curl -fsSL https://ftp.gnu.org/gnu/gcc/gcc-5.2.0/gcc-5.2.0.tar.gz >/tmp/gcc.tar.gz +RUN tar -C /usr/local -xzf /tmp/gcc.tar.gz +RUN cd /usr/local/gcc-5.2.0 && ./contrib/download_prerequisites && mkdir -p build \ + && cd build && ../configure --disable-multilib && make && make install diff --git a/ci/release/builders/aws_ensure.sh b/ci/release/builders/aws_ensure.sh new file mode 100755 index 000000000..dfeb3443c --- /dev/null +++ b/ci/release/builders/aws_ensure.sh @@ -0,0 +1,242 @@ +#!/bin/sh +set -eu +cd -- "$(dirname "$0")/../../.." +. ./ci/sub/lib.sh + +help() { + cat </dev/null \ + | jq -r .SecurityGroups[0].GroupId) + if [ -z "$SG_ID" ]; then + SG_ID=$(sh_c aws ec2 create-security-group \ + --group-name ssh \ + --description ssh \ + --vpc-id "$VPC_ID" | jq -r .GroupId) + fi + + header security-group-ingress + SG_RULES_COUNT=$(aws ec2 describe-security-groups --group-names ssh \ + | jq -r '.SecurityGroups[0].IpPermissions | length') + if [ "$SG_RULES_COUNT" -eq 0 ]; then + sh_c aws ec2 authorize-security-group-ingress \ + --group-id "$SG_ID" \ + --protocol tcp \ + --port 22 \ + --cidr 0.0.0.0/0 >/dev/null + fi + + header linux-amd64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-linux-amd64' \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-0d593311db5abb72b \ + --count=1 \ + --instance-type=t2.small \ + --security-groups=ssh \ + "--key-name=$KEY_NAME" \ + --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=d2-builder-linux-amd64}]"' \ + '"ResourceType=volume,Tags=[{Key=Name,Value=d2-builder-linux-amd64}]"' >/dev/null + fi + while true; do + dnsname=$(sh_c aws ec2 describe-instances \ + --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-linux-amd64' \ + | jq -r '.Reservations[].Instances[].PublicDnsName') + if [ -n "$dnsname" ]; then + log "TSTRUCT_LINUX_AMD64_BUILDER=ec2-user@$dnsname" + export TSTRUCT_LINUX_AMD64_BUILDER=ec2-user@$dnsname + break + fi + sleep 5 + done + + header linux-arm64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-linux-arm64' \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-0efabcf945ffd8831 \ + --count=1 \ + --instance-type=t4g.small \ + --security-groups=ssh \ + "--key-name=$KEY_NAME" \ + --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=d2-builder-linux-arm64}]"' \ + '"ResourceType=volume,Tags=[{Key=Name,Value=d2-builder-linux-arm64}]"' >/dev/null + fi + while true; do + dnsname=$(sh_c aws ec2 describe-instances \ + --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-linux-arm64' \ + | jq -r '.Reservations[].Instances[].PublicDnsName') + if [ -n "$dnsname" ]; then + log "TSTRUCT_LINUX_ARM64_BUILDER=ec2-user@$dnsname" + export TSTRUCT_LINUX_ARM64_BUILDER=ec2-user@$dnsname + break + fi + sleep 5 + done + + header "macos-amd64-host" + MACOS_AMD64_HOST_ID=$(aws ec2 describe-hosts --filter 'Name=state,Values=pending,available' 'Name=tag:Name,Values=d2-builder-macos-amd64' | jq -r '.Hosts[].HostId') + if [ -z "$MACOS_AMD64_HOST_ID" ]; then + MACOS_AMD64_HOST_ID=$(sh_c aws ec2 allocate-hosts --instance-type mac1.metal --quantity 1 --availability-zone us-west-2a \ + --tag-specifications '"ResourceType=dedicated-host,Tags=[{Key=Name,Value=d2-builder-macos-amd64}]"' \ + | jq -r .HostIds[0]) + fi + + header "macos-arm64-host" + MACOS_ARM64_HOST_ID=$(aws ec2 describe-hosts --filter 'Name=state,Values=pending,available' 'Name=tag:Name,Values=d2-builder-macos-arm64' | jq -r '.Hosts[].HostId') + if [ -z "$MACOS_ARM64_HOST_ID" ]; then + MACOS_ARM64_HOST_ID=$(sh_c aws ec2 allocate-hosts --instance-type mac2.metal --quantity 1 --availability-zone us-west-2a \ + --tag-specifications '"ResourceType=dedicated-host,Tags=[{Key=Name,Value=d2-builder-macos-amd64}]"' \ + | jq -r .HostIds[0]) + fi + + header macos-amd64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-macos-amd64' \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-0dd2ded7568750663 \ + --count=1 \ + --instance-type=mac1.metal \ + --security-groups=ssh \ + "--key-name=$KEY_NAME" \ + --placement "Tenancy=host,HostId=$MACOS_AMD64_HOST_ID" \ + --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=d2-builder-macos-amd64}]"' \ + '"ResourceType=volume,Tags=[{Key=Name,Value=d2-builder-macos-amd64}]"' >/dev/null + fi + while true; do + dnsname=$(sh_c aws ec2 describe-instances \ + --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-macos-amd64' \ + | jq -r '.Reservations[].Instances[].PublicDnsName') + if [ -n "$dnsname" ]; then + log "TSTRUCT_MACOS_AMD64_BUILDER=ec2-user@$dnsname" + export TSTRUCT_MACOS_AMD64_BUILDER=ec2-user@$dnsname + break + fi + sleep 5 + done + + header macos-arm64 + state=$(aws ec2 describe-instances --filters \ + 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-macos-arm64' \ + | jq -r '.Reservations[].Instances[].State.Name') + if [ -z "$state" ]; then + sh_c aws ec2 run-instances \ + --image-id=ami-0af0516ff2c43dbbe \ + --count=1 \ + --instance-type=mac2.metal \ + --security-groups=ssh \ + "--key-name=$KEY_NAME" \ + --placement "Tenancy=host,HostId=$MACOS_ARM64_HOST_ID" \ + --tag-specifications '"ResourceType=instance,Tags=[{Key=Name,Value=d2-builder-macos-arm64}]"' \ + '"ResourceType=volume,Tags=[{Key=Name,Value=d2-builder-macos-arm64}]"' >/dev/null + fi + while true; do + dnsname=$(sh_c aws ec2 describe-instances \ + --filters 'Name=instance-state-name,Values=pending,running,stopping,stopped' 'Name=tag:Name,Values=d2-builder-macos-arm64' \ + | jq -r '.Reservations[].Instances[].PublicDnsName') + if [ -n "$dnsname" ]; then + log "TSTRUCT_MACOS_ARM64_BUILDER=ec2-user@$dnsname" + export TSTRUCT_MACOS_ARM64_BUILDER=ec2-user@$dnsname + break + fi + sleep 5 + done +} + +init_rhosts() { + header linux-amd64 + RHOST=$TSTRUCT_LINUX_AMD64_BUILDER init_rhost_linux + header linux-arm64 + RHOST=$TSTRUCT_LINUX_ARM64_BUILDER init_rhost_linux + header macos-amd64 + RHOST=$TSTRUCT_MACOS_AMD64_BUILDER init_rhost_macos + header macos-arm64 + RHOST=$TSTRUCT_MACOS_ARM64_BUILDER init_rhost_macos + + COLOR=2 header summary + log "export TSTRUCT_LINUX_AMD64_BUILDER=$TSTRUCT_LINUX_AMD64_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_ARM64_BUILDER=$TSTRUCT_MACOS_ARM64_BUILDER" +} + +init_rhost_linux() { + while true; do + if sh_c ssh "$RHOST" :; then + break + fi + sleep 5 + done + sh_c ssh "$RHOST" 'sudo yum upgrade -y' + sh_c ssh "$RHOST" 'sudo yum install -y docker' + sh_c ssh "$RHOST" 'sudo systemctl start docker' + sh_c ssh "$RHOST" 'sudo systemctl enable docker' + sh_c ssh "$RHOST" 'sudo usermod -a -G docker ec2-user' + sh_c ssh "$RHOST" 'sudo reboot' || true +} + +init_rhost_macos() { + while true; do + if sh_c ssh "$RHOST" :; then + break + fi + sleep 5 + done + sh_c ssh "$RHOST" '": | /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""' + sh_c ssh "$RHOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew update' + sh_c ssh "$RHOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew upgrade' + sh_c ssh "$RHOST" 'PATH="/usr/local/bin:/opt/homebrew/bin:\$PATH" brew install go' +} + +main "$@" diff --git a/ci/release/builders/aws_ssh_copy_id.sh b/ci/release/builders/aws_ssh_copy_id.sh new file mode 100755 index 000000000..d50a96d9d --- /dev/null +++ b/ci/release/builders/aws_ssh_copy_id.sh @@ -0,0 +1,43 @@ +#!/bin/sh +set -eu +cd -- "$(dirname "$0")/../../.." +. ./ci/sub/lib.sh + +help() { + cat <./install.sh <\> ./install.sh +sh_c cat ./ci/release/_install.sh \ + \| sed -n "'/cd -- \"\$(dirname/,/cd -/!p'" \>\> install.sh +sh_c chmod -w install.sh diff --git a/ci/release/release.sh b/ci/release/release.sh new file mode 100755 index 000000000..517ae778d --- /dev/null +++ b/ci/release/release.sh @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu +cd -- "$(dirname "$0")/../.." + +./ci/sub/release/release.sh "$@" diff --git a/ci/release/template/LICENSE.txt b/ci/release/template/LICENSE.txt new file mode 120000 index 000000000..6932c6369 --- /dev/null +++ b/ci/release/template/LICENSE.txt @@ -0,0 +1 @@ +../../../LICENSE.txt \ No newline at end of file diff --git a/ci/release/template/Makefile b/ci/release/template/Makefile new file mode 100644 index 000000000..5b9ba1353 --- /dev/null +++ b/ci/release/template/Makefile @@ -0,0 +1,12 @@ +.POSIX: +.SILENT: + +PREFIX = $(DESTDIR)/usr/local + +.PHONY: install +install: + PREFIX='$(PREFIX)' ./scripts/install.sh + +.PHONY: uninstall +uninstall: + PREFIX='$(PREFIX)' ./scripts/uninstall.sh diff --git a/ci/release/template/README.md.sh b/ci/release/template/README.md.sh new file mode 100755 index 000000000..ec7cb8a11 --- /dev/null +++ b/ci/release/template/README.md.sh @@ -0,0 +1,28 @@ +#!/bin/sh +set -eu + +cat < "$seed_file" + shuf -i "$range" -n 1 --random-source="$seed_file" +} + +pick() { + seed="$1" + shift + i="$(rand "$seed" "1-$#")" + eval "_echo \"\$$i\"" +} + +tput() { + if [ -n "$TERM" ]; then + command tput "$@" + fi +} + +setaf() { + tput setaf "$1" + shift + printf '%s' "$*" + tput sgr0 +} + +_echo() { + printf '%s\n' "$*" +} + +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 +} + +echop() { + prefix="$1" + shift + + if [ "$#" -gt 0 ]; then + printfp "$prefix" "%s\n" "$*" + else + printfp "$prefix" + printf '\n' + fi +} + +printfp() {( + prefix="$1" + shift + + if [ -z "${COLOR:-}" ]; then + COLOR="$(get_rand_color "$prefix")" + fi + printf '%s' "$(setaf "$COLOR" "$prefix")" + + if [ $# -gt 0 ]; then + printf ': ' + printf "$@" + fi +)} + +catp() { + prefix="$1" + shift + + printfp "$prefix" + printf ': ' + read -r line + _echo "$line" + + indent=$(repeat ' ' 2) + sed "s/^/$indent/" +} + +repeat() { + char="$1" + times="$2" + seq -s "$char" "$times" | tr -d '[:digit:]' +} + +strlen() { + printf %s "$1" | wc -c +} + +echoerr() { + COLOR=1 echop err "$*" >&2 +} + +caterr() { + COLOR=1 catp err "$@" >&2 +} + +printferr() { + COLOR=1 printfp err "$@" >&2 +} + +logp() { + echop "$@" >&2 +} + +logfp() { + printfp "$@" >&2 +} + +logpcat() { + catp "$@" >&2 +} + +log() { + COLOR=5 logp log "$@" +} + +logf() { + COLOR=5 logfp log "$@" +} + +logcat() { + COLOR=5 catp log "$@" >&2 +} + +sh_c() { + COLOR=3 logp exec "$*" + if [ -z "${DRY_RUN-}" ]; then + "$@" + fi +} + +header() { + logp "/* $1 */" +} diff --git a/ci/release/template/scripts/uninstall.sh b/ci/release/template/scripts/uninstall.sh new file mode 100755 index 000000000..d195463f3 --- /dev/null +++ b/ci/release/template/scripts/uninstall.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -eu +cd -- "$(dirname "$0")/.." +. ./scripts/lib.sh + +main() { + if [ ! -e "${PREFIX-}" ]; then + echoerr "\$PREFIX must be set to a unix prefix directory from which to uninstall d2 like /usr/local" + return 1 + 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 7ff838089..374e1af41 160000 --- a/ci/sub +++ b/ci/sub @@ -1 +1 @@ -Subproject commit 7ff8380897435e73d53e329a7cd39dc38c7ad227 +Subproject commit 374e1af41579710c2728ef4f657bebea79bbaa60 diff --git a/cmd/d2/help.go b/cmd/d2/help.go index affc2abf5..961c9c275 100644 --- a/cmd/d2/help.go +++ b/cmd/d2/help.go @@ -27,7 +27,7 @@ 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 -See more docs at https://oss.terrastruct.com/d2 +See more docs and the source code at https://oss.terrastruct.com/d2 `, ms.Name, ms.FlagHelp()) } diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index 5e38e4252..9a4555128 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -19,7 +19,7 @@ type elkPlugin struct{} func (p elkPlugin) Info(context.Context) (*PluginInfo, error) { return &PluginInfo{ - Name: "ELK", + Name: "elk", ShortHelp: "Eclipse Layout Kernel (ELK) with the Layered algorithm.", LongHelp: `ELK is a layout engine offered by Eclipse. Originally written in Java, it has been ported to Javascript and cross-compiled into D2. diff --git a/d2renderers/d2svg/class.go b/d2renderers/d2svg/class.go index befe5c47e..af71ac1af 100644 --- a/d2renderers/d2svg/class.go +++ b/d2renderers/d2svg/class.go @@ -97,8 +97,8 @@ func visibilityToken(visibility string) string { } func drawClass(writer io.Writer, targetShape d2target.Shape) { - fmt.Fprintf(writer, ``, - targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height) + fmt.Fprintf(writer, ``, + targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(targetShape)) box := geo.NewBox( geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)), diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index d8f1bb2d0..ec8e33d55 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -37,6 +37,9 @@ const ( var multipleOffset = geo.NewVector(10, -10) +//go:embed style.css +var styleCSS string + //go:embed github-markdown.css var mdCSS string @@ -446,12 +449,12 @@ func drawConnection(writer io.Writer, connection d2target.Connection, markers ma } } -func renderOval(tl *geo.Point, width, height float64) string { +func renderOval(tl *geo.Point, width, height float64, style string) string { rx := width / 2 ry := height / 2 cx := tl.X + rx cy := tl.Y + ry - return fmt.Sprintf(``, cx, cy, rx, ry) + return fmt.Sprintf(``, cx, cy, rx, ry, style) } func defineShadowFilter(writer io.Writer) { @@ -487,7 +490,7 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) error { } } - fmt.Fprintf(writer, ``, style, shadowAttr) + fmt.Fprintf(writer, ``, shadowAttr) var multipleTL *geo.Point if targetShape.Multiple { @@ -497,20 +500,22 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) error { switch targetShape.Type { case d2target.ShapeClass: drawClass(writer, targetShape) + fmt.Fprintf(writer, ``) return nil case d2target.ShapeSQLTable: drawTable(writer, targetShape) + fmt.Fprintf(writer, ``) return nil case d2target.ShapeOval: if targetShape.Multiple { - fmt.Fprint(writer, renderOval(multipleTL, width, height)) + fmt.Fprint(writer, renderOval(multipleTL, width, height, style)) } - fmt.Fprint(writer, renderOval(tl, width, height)) + fmt.Fprint(writer, renderOval(tl, width, height, style)) case d2target.ShapeImage: - fmt.Fprintf(writer, ``, + fmt.Fprintf(writer, ``, targetShape.Icon.String(), - targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height) + targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style) // TODO should standardize "" to rectangle case d2target.ShapeRectangle, "": @@ -550,23 +555,23 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) error { strings.Join(rightPolygonPoints, ""), darkerColor) } if targetShape.Multiple { - fmt.Fprintf(writer, ``, - targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height) + fmt.Fprintf(writer, ``, + targetShape.Pos.X+10, targetShape.Pos.Y-10, targetShape.Width, targetShape.Height, style) } - fmt.Fprintf(writer, ``, - targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height) + fmt.Fprintf(writer, ``, + targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, style) case d2target.ShapeText, d2target.ShapeCode: default: if targetShape.Multiple { multiplePathData := shape.NewShape(shapeType, geo.NewBox(multipleTL, width, height)).GetSVGPathData() for _, pathData := range multiplePathData { - fmt.Fprintf(writer, ``, pathData) + fmt.Fprintf(writer, ``, pathData, style) } } for _, pathData := range s.GetSVGPathData() { - fmt.Fprintf(writer, ``, pathData) + fmt.Fprintf(writer, ``, pathData, style) } } @@ -837,18 +842,11 @@ func Render(diagram *d2target.Diagram) ([]byte, error) { buf := &bytes.Buffer{} _, _ = setViewbox(buf, diagram) - buf.WriteString(``) +`, styleCSS)) hasMarkdown := false for _, s := range diagram.Shapes { diff --git a/d2renderers/d2svg/style.css b/d2renderers/d2svg/style.css new file mode 100644 index 000000000..f5cffa9cc --- /dev/null +++ b/d2renderers/d2svg/style.css @@ -0,0 +1,8 @@ +.shape { + shape-rendering: geometricPrecision; + stroke-linejoin: round; +} +.connection { + stroke-linecap: round; + stroke-linejoin: round; +} diff --git a/d2renderers/d2svg/table.go b/d2renderers/d2svg/table.go index de378793e..ce9e79536 100644 --- a/d2renderers/d2svg/table.go +++ b/d2renderers/d2svg/table.go @@ -98,8 +98,8 @@ func constraintAbbr(constraint string) string { } func drawTable(writer io.Writer, targetShape d2target.Shape) { - fmt.Fprintf(writer, ``, - targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height) + fmt.Fprintf(writer, ``, + targetShape.Pos.X, targetShape.Pos.Y, targetShape.Width, targetShape.Height, shapeStyle(targetShape)) box := geo.NewBox( geo.NewPoint(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)), diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index 2eecea2e3..513451247 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -2,6 +2,7 @@ package e2etests import ( "context" + "encoding/xml" "fmt" "io/ioutil" "os" @@ -127,6 +128,11 @@ func run(t *testing.T, tc testCase) { t.Fatal(err) } + var xmlParsed interface{} + if err := xml.Unmarshal(svgBytes, &xmlParsed); err != nil { + t.Fatalf("invalid SVG: %v", err) + } + err = diff.Testdata(filepath.Join(dataPath, "board"), diagram) if err != nil { ioutil.WriteFile(pathGotSVG, svgBytes, 0600) diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go index 604a08ee7..c9a1a87fc 100644 --- a/e2etests/stable_test.go +++ b/e2etests/stable_test.go @@ -816,6 +816,20 @@ a line of text and an | a -> md -> b +`, + }, + { + name: "class", + script: `manager: BatchManager { + shape: class + -num: int + -timeout: int + -pid + + +getStatus(): Enum + +getJobs(): "Job[]" + +setTimeout(seconds int) +} `, }, } diff --git a/e2etests/testdata/sanity/1_to_2/dagre/sketch.exp.svg b/e2etests/testdata/sanity/1_to_2/dagre/sketch.exp.svg index 7a5f6396e..73c194056 100644 --- a/e2etests/testdata/sanity/1_to_2/dagre/sketch.exp.svg +++ b/e2etests/testdata/sanity/1_to_2/dagre/sketch.exp.svg @@ -5,15 +5,16 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="486" height="552" viewBox="-100 -100 486 552">abc abc abc abc ab ab ab ab acbd acbd acbd acbd ab +ab helloab +ab hellorectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud rectanglesquarepageparallelogramdocumentcylinderqueuepackagestepcalloutstored_datapersondiamondovalcirclehexagoncloud @@ -21,7 +22,7 @@ width="1539" height="824" viewBox="-100 -100 1539 824"> @@ -21,7 +22,7 @@ width="959" height="1084" viewBox="-88 -88 959 1084">cba cba cba cba abcdefghijklmno abcdefghijklmno abcdefghijklmno abcdefghijklmno aaadddeeebbbccc +aaadddeeebbbccc 111 diff --git a/e2etests/testdata/stable/chaos1/elk/sketch.exp.svg b/e2etests/testdata/stable/chaos1/elk/sketch.exp.svg index bcc32e178..93da4fd1d 100644 --- a/e2etests/testdata/stable/chaos1/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/chaos1/elk/sketch.exp.svg @@ -5,15 +5,16 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="888" height="622" viewBox="-88 -88 888 622">aaadddeeebbbccc +aaadddeeebbbccc 111 diff --git a/e2etests/testdata/stable/chaos2/dagre/sketch.exp.svg b/e2etests/testdata/stable/chaos2/dagre/sketch.exp.svg index 09769e025..9dae57573 100644 --- a/e2etests/testdata/stable/chaos2/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/chaos2/dagre/sketch.exp.svg @@ -5,13 +5,14 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1317" height="1854" viewBox="-100 -100 1317 1854">aabbllmm

nn

-
oocciikkdd

gg

-
hhjj

ee

-
ff +aabbllmm

nn

+
oocciikkdd

gg

+
hhjj

ee

+
ff 11 diff --git a/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg b/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg index 1334d762c..2e7c5bf83 100644 --- a/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/chaos2/elk/sketch.exp.svg @@ -5,13 +5,14 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1856" height="1097" viewBox="-88 -88 1856 1097">aabbllmm

nn

-
oocciikkdd

gg

-
hhjj

ee

-
ff +aabbllmm

nn

+
oocciikkdd

gg

+
hhjj

ee

+
ff 11 diff --git a/e2etests/testdata/stable/child_parent_edges/dagre/sketch.exp.svg b/e2etests/testdata/stable/child_parent_edges/dagre/sketch.exp.svg index 49478c030..e7cf23c0f 100644 --- a/e2etests/testdata/stable/child_parent_edges/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/child_parent_edges/dagre/sketch.exp.svg @@ -5,15 +5,16 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="694" height="626" viewBox="-100 -100 694 626">abcd abcd abcd abcd abc abc abc abc BatchManager- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +void \ No newline at end of file diff --git a/e2etests/testdata/stable/class/elk/board.exp.json b/e2etests/testdata/stable/class/elk/board.exp.json new file mode 100644 index 000000000..592a921c2 --- /dev/null +++ b/e2etests/testdata/stable/class/elk/board.exp.json @@ -0,0 +1,75 @@ +{ + "name": "", + "shapes": [ + { + "id": "manager", + "type": "class", + "pos": { + "x": 12, + "y": 12 + }, + "width": 339, + "height": 368, + "level": 1, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#FFFFFF", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "fields": [ + { + "name": "num", + "type": "int", + "visibility": "private" + }, + { + "name": "timeout", + "type": "int", + "visibility": "private" + }, + { + "name": "pid", + "type": "", + "visibility": "private" + } + ], + "methods": [ + { + "name": "getStatus()", + "return": "Enum", + "visibility": "public" + }, + { + "name": "getJobs()", + "return": "Job[]", + "visibility": "public" + }, + { + "name": "setTimeout(seconds int)", + "return": "void", + "visibility": "public" + } + ], + "columns": null, + "label": "BatchManager", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 150, + "labelHeight": 36 + } + ], + "connections": [] +} diff --git a/e2etests/testdata/stable/class/elk/sketch.exp.svg b/e2etests/testdata/stable/class/elk/sketch.exp.svg new file mode 100644 index 000000000..a324244e9 --- /dev/null +++ b/e2etests/testdata/stable/class/elk/sketch.exp.svg @@ -0,0 +1,36 @@ + +BatchManager- +num +int- +timeout +int- +pid ++ +getStatus() +Enum+ +getJobs() +Job[]+ +setTimeout(seconds int) +void \ No newline at end of file diff --git a/e2etests/testdata/stable/code_snippet/dagre/sketch.exp.svg b/e2etests/testdata/stable/code_snippet/dagre/sketch.exp.svg index 2fe2d7b48..7c354e1bf 100644 --- a/e2etests/testdata/stable/code_snippet/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/code_snippet/dagre/sketch.exp.svg @@ -5,15 +5,16 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="955" height="818" viewBox="-100 -100 955 818">// RegisterHash registers a function that returns a new instance of the given +// RegisterHash registers a function that returns a new instance of the given // hash function. This is intended to be called from the init function in // packages that implement hash functions. func RegisterHash(h Hash, f func() hash.Hash) { @@ -21,7 +22,7 @@ width="955" height="818" viewBox="-100 -100 955 818">// RegisterHash registers a function that returns a new instance of the given +// RegisterHash registers a function that returns a new instance of the given // hash function. This is intended to be called from the init function in // packages that implement hash functions. func RegisterHash(h Hash, f func() hash.Hash) { @@ -21,7 +22,7 @@ width="1382" height="366" viewBox="-88 -88 1382 366">acfbdhg acfbdhg acfbdhg acfbdhg agdfbhec agdfbhec agdfbhec agdfbhec abcdefghijklmnopq abcdefghijklmnopq abcdefghijklmnopq abcdefghijklmnopq finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot finallyatreeandnodessomemoremanythenhereyouhavehierarchyanotherofnestingtreesatreeinsidehierarchyroot

Markdown: Syntax

+

Markdown: Syntax

  • Overview
      @@ -1046,7 +1047,7 @@ title for the link, surrounded in quotes. For example:

      Code

      Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example:

      -
ab

Markdown: Syntax

+

Markdown: Syntax

  • Overview
      @@ -1046,7 +1047,7 @@ title for the link, surrounded in quotes. For example:

      Code

      Unlike a pre-formatted code block, a code span indicates code within a normal paragraph. For example:

      -
ab

Note: This document is itself written using Markdown; you +

Note: This document is itself written using Markdown; you can see the source for it by adding '.text' to the URL.


Overview

-
ab

Note: This document is itself written using Markdown; you +

Note: This document is itself written using Markdown; you can see the source for it by adding '.text' to the URL.


Overview

-
ab aabbccddllffwwyyadnniijjkkssuurmeemmmmgghhzzooppqqrrttvvxxabac +aabbccddllffwwyyadnniijjkkssuurmeemmmmgghhzzooppqqrrttvvxxabac 1 diff --git a/e2etests/testdata/stable/investigate/elk/sketch.exp.svg b/e2etests/testdata/stable/investigate/elk/sketch.exp.svg index 99055f29f..e4f8a8af2 100644 --- a/e2etests/testdata/stable/investigate/elk/sketch.exp.svg +++ b/e2etests/testdata/stable/investigate/elk/sketch.exp.svg @@ -5,15 +5,16 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="4784" height="870" viewBox="-88 -75 4784 870">aabbccddllffwwyyadnniijjkkssuurmeemmmmgghhzzooppqqrrttvvxxabac +aabbccddllffwwyyadnniijjkkssuurmeemmmmgghhzzooppqqrrttvvxxabac 1 diff --git a/e2etests/testdata/stable/large_arch/dagre/sketch.exp.svg b/e2etests/testdata/stable/large_arch/dagre/sketch.exp.svg index 9e77f80a3..2f9acf15e 100644 --- a/e2etests/testdata/stable/large_arch/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/large_arch/dagre/sketch.exp.svg @@ -5,15 +5,16 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3244" height="1780" viewBox="-100 -100 3244 1780">abcdefghiqrjmnoszaabbeeffggklptuwxyccddv abcdefghiqrjmnoszaabbeeffggklptuwxyccddv abcdefghiqrjmnoszaabbeeffggklptuwxyccddv abcdefghiqrjmnoszaabbeeffggklptuwxyccddv
    +
    • Overview
      • Philosophy
      • @@ -801,7 +802,7 @@ width="579" height="752" viewBox="-100 -100 579 752">
          +
          • Overview
            • Philosophy
            • @@ -801,7 +802,7 @@ width="1005" height="326" viewBox="-88 -88 1005 326">
                +
                • Overview ok this is all measured
                  • Philosophy
                  • @@ -797,7 +798,7 @@ width="445" height="728" viewBox="-100 -100 445 728">
                      +
                      • Overview ok this is all measured
                        • Philosophy
                        • @@ -797,7 +798,7 @@ width="871" height="326" viewBox="-88 -88 871 326">
                            +
                            • Overview
                              • Philosophy
                              • @@ -822,7 +823,7 @@ width="547" height="1164" viewBox="-100 -100 547 1164">
                                  +
                                  • Overview
                                    • Philosophy
                                    • @@ -822,7 +823,7 @@ width="973" height="712" viewBox="-88 -88 973 712">

                                      List items may consist of multiple paragraphs. Each subsequent +

                                      List items may consist of multiple paragraphs. Each subsequent paragraph in a list item must be indented by either 4 spaces or one tab:

                                        @@ -820,7 +821,7 @@ sit amet, consectetuer adipiscing elit.

                                        Another item in the same list.

                                    -
                                  ab

                                  List items may consist of multiple paragraphs. Each subsequent +

                                  List items may consist of multiple paragraphs. Each subsequent paragraph in a list item must be indented by either 4 spaces or one tab:

                                    @@ -820,7 +821,7 @@ sit amet, consectetuer adipiscing elit.

                                    Another item in the same list.

                                -
                                ab

                                Markdown: Syntax

                                -
                                ab

                                Markdown: Syntax

                                +
                                ab

                                Markdown: Syntax

                                -
                                ab

                                Markdown: Syntax

                                +
                                ab

                                Every frustum longs to be a cone

                                +

                                Every frustum longs to be a cone

                                • A continuing flow of paper is sufficient to continue the flow of paper
                                • Please remain calm, it's no use both of us being hysterical at the same time
                                • Visits always give pleasure: if not on arrival, then on the departure

                                Festivity Level 1: Your guests are chatting amiably with each other.

                                -
                                xy

                                Every frustum longs to be a cone

                                +

                                Every frustum longs to be a cone

                                • A continuing flow of paper is sufficient to continue the flow of paper
                                • Please remain calm, it's no use both of us being hysterical at the same time
                                • Visits always give pleasure: if not on arrival, then on the departure

                                Festivity Level 1: Your guests are chatting amiably with each other.

                                -
                                xy
                                {
                                +
                                {
                                 	fenced: "block",
                                 	of: "json",
                                 }
                                 
                                -
                                ab
                                {
                                +
                                {
                                 	fenced: "block",
                                 	of: "json",
                                 }
                                 
                                -
                                ab

                                a line of text and an

                                +

                                a line of text and an

                                {
                                 	indented: "block",
                                 	of: "json",
                                 }
                                 
                                -
                                ab

                                a line of text and an

                                +

                                a line of text and an

                                {
                                 	indented: "block",
                                 	of: "json",
                                 }
                                 
                                -
                                ab

                                code

                                -
                                ab

                                code

                                +
                                ab

                                code

                                -
                                ab

                                code

                                +
                                ab thisgoesmultiple linesthisgoesmultiple linesthisgoesmultiple linesthisgoesmultiple linesabcdefghijklmnopqrstuvw abcdefghijklmnopqrstuvw abcdefghijklmnopqrstuvw abcdefghijklmnopqrstuvw abcdefghijklmnopqrstu abcdefghijklmnopqrstu abcdefghijklmnopqrstu abcdefghijklmnopqrstu acdefgbh acdefgbh acdefgbh acdefgbh topabcbottomstartend topabcbottomstartend topabcbottomstartend topabcbottomstartend

                                A paragraph is simply one or more consecutive lines of text, separated +

                                A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.

                                -
                                ab

                                A paragraph is simply one or more consecutive lines of text, separated +

                                A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. (A blank line is any line that looks like a blank line -- a line containing nothing but spaces or tabs is considered blank.) Normal paragraphs should not be indented with spaces or tabs.

                                -
                                ab

                                Here is an example of AppleScript:

                                +

                                Here is an example of AppleScript:

                                tell application "Foo"
                                     beep
                                 end tell
                                 

                                A code block continues until it reaches a line that is not indented (or the end of the article).

                                -
                                ab

                                Here is an example of AppleScript:

                                +

                                Here is an example of AppleScript:

                                tell application "Foo"
                                     beep
                                 end tell
                                 

                                A code block continues until it reaches a line that is not indented (or the end of the article).

                                -
                                ab rectanglesquare rectanglesquare rectanglesquare rectanglesquare acbl1l2c1l2c3l2c2l3c1l3c2l4bacacbabcc1c2c3abc acbl1l2c1l2c3l2c2l3c1l3c2l4bacacbabcc1c2c3abc acbl1l2c1l2c3l2c2l3c1l3c2l4bacacbabcc1c2c3abc acbl1l2c1l2c3l2c2l3c1l3c2l4bacacbabcc1c2c3abc AKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTNDAKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTNDAKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTNDAKHIALFLGAMSTNAZCANVNMUTARLAMOOKTXORCOKSNEWYCTMANYRIDEMDNJPANCSCIDMTWAILINIAMIKYWIOHMNSDVAWVMENHVTND