diff --git a/.gitignore b/.gitignore index 5d9b698..d7ade9d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,3 @@ tests/examples/cur tests/examples/diff web/elm-stuff static/web.js -stack.yaml.lock diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c8749bb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,186 +0,0 @@ -# This is the complex Travis configuration, which is intended for use -# on open source libraries which need compatibility across multiple GHC -# versions, must work with cabal-install, and should be -# cross-platform. For more information and other options, see: -# -# https://docs.haskellstack.org/en/stable/travis_ci/ -# -# Copy these contents into the root directory of your Github project in a file -# named .travis.yml - -# Use new container infrastructure to enable caching -sudo: false - -# Do not choose a language; we provide our own build tools. -language: generic - -# Caching so the next build will be fast too. -cache: - directories: - - $HOME/.ghc - - $HOME/.cabal - - $HOME/.stack - - $TRAVIS_BUILD_DIR/.stack-work - -# The different configurations we want to test. We have BUILD=cabal which uses -# cabal-install, and BUILD=stack which uses Stack. More documentation on each -# of those below. -# -# We set the compiler values here to tell Travis to use a different -# cache file per set of arguments. -# -# If you need to have different apt packages for each combination in the -# matrix, you can use a line such as: -# addons: {apt: {packages: [libfcgi-dev,libgmp-dev]}} -matrix: - include: - # We grab the appropriate GHC and cabal-install versions from hvr's PPA. See: - # https://github.com/hvr/multi-ghc-travis - - env: BUILD=cabal GHCVER=8.4.3 CABALVER=2.2 HAPPYVER=1.19.5 ALEXVER=3.1.7 - compiler: ": #GHC 8.4.3" - addons: {apt: {packages: [cabal-install-2.2,ghc-8.4.3,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} - - - env: BUILD=cabal GHCVER=8.6.5 CABALVER=2.4 HAPPYVER=1.19.5 ALEXVER=3.1.7 - compiler: ": #GHC 8.6.5" - addons: {apt: {packages: [cabal-install-2.4,ghc-8.6.5,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} - - # disable cabal-install-3.0 builds because they seem incompatible with this - # travis config: - # - # cabal: --enable-tests was specified, but tests can't be enabled in a remote package - - #- env: BUILD=cabal GHCVER=8.8.2 CABALVER=3.0 HAPPYVER=1.19.5 ALEXVER=3.1.7 - # compiler: ": #GHC 8.8.2" - # addons: {apt: {packages: [cabal-install-3.0,ghc-8.8.2,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} - - ## Build with the newest GHC and cabal-install. This is an accepted failure, - ## see below. - #- env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7 - # compiler: ": #GHC HEAD" - # addons: {apt: {packages: [cabal-install-head,ghc-head,happy-1.19.5,alex-3.1.7], sources: [hvr-ghc]}} - - # The Stack builds. We can pass in arbitrary Stack arguments via the ARGS - # variable, such as using --stack-yaml to point to a different file. - - env: BUILD=stack ARGS="" UPLOAD=yes - compiler: ": #stack default" - addons: {apt: {packages: [libgmp-dev, imagemagick]}} - - # Nightly builds are allowed to fail - - env: BUILD=stack ARGS="--resolver nightly" - compiler: ": #stack nightly" - addons: {apt: {packages: [libgmp-dev, imagemagick]}} - - # Build on macOS in addition to Linux - - env: BUILD=stack ARGS="" UPLOAD=yes - compiler: ": #stack default osx" - os: osx - - - env: BUILD=stack ARGS="--resolver nightly" - compiler: ": #stack nightly osx" - os: osx - - allow_failures: - # - env: BUILD=cabal GHCVER=head CABALVER=head HAPPYVER=1.19.5 ALEXVER=3.1.7 - - env: BUILD=stack ARGS="--resolver nightly" - -before_install: -# Using compiler above sets CC to an invalid value, so unset it -- unset CC - -# We want to always allow newer versions of packages when building on GHC HEAD -- CABALARGS="" -- if [ "x$GHCVER" = "xhead" ]; then CABALARGS=--allow-newer; fi - -# Download and unpack the stack executable -- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$HOME/.local/bin:/opt/alex/$ALEXVER/bin:/opt/happy/$HAPPYVER/bin:$HOME/.cabal/bin:$PATH -- mkdir -p ~/.local/bin -- | - if [ `uname` = "Darwin" ] - then - travis_retry curl -L https://get.haskellstack.org/stable/osx-x86_64.tar.gz | tar xz --strip-components=1 --include '*/stack' -C ~/.local/bin - else - travis_retry curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' - fi - - # Use the more reliable S3 mirror of Hackage - mkdir -p $HOME/.cabal - echo 'remote-repo: hackage.haskell.org:http://hackage.fpcomplete.com/' > $HOME/.cabal/config - echo 'remote-repo-cache: $HOME/.cabal/packages' >> $HOME/.cabal/config -- sh tools/install-ghr.sh - -install: -- echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]" -- if [ -f configure.ac ]; then autoreconf -i; fi -- | - set -ex - case "$BUILD" in - stack) - # Add in extra-deps for older snapshots, as necessary - # - # This is disabled by default, as relying on the solver like this can - # make builds unreliable. Instead, if you have this situation, it's - # recommended that you maintain multiple stack-lts-X.yaml files. - - #stack --no-terminal --install-ghc $ARGS test --bench --dry-run || ( \ - # stack --no-terminal $ARGS build cabal-install && \ - # stack --no-terminal $ARGS solver --update-config) - - # Build the dependencies - stack --no-terminal --install-ghc $ARGS test --bench --only-dependencies - ;; - cabal) - cabal --version - travis_retry cabal update - - # Get the list of packages from the stack.yaml file. Note that - # this will also implicitly run hpack as necessary to generate - # the .cabal files needed by cabal-install. - PACKAGES=$(stack --install-ghc query locals | grep '^ *path' | sed 's@^ *path:@@') - - cabal install --only-dependencies --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES - ;; - esac - set +ex - -script: -- | - set -ex - case "$BUILD" in - stack) - stack --no-terminal $ARGS test --bench --no-run-benchmarks - stack --no-terminal $ARGS build - make compare STACKARGS="$ARGS" - ;; - cabal) - cabal install --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES - - ORIGDIR=$(pwd) - for dir in $PACKAGES - do - cd $dir - cabal check || [ "$CABALVER" == "1.16" ] - cabal sdist - PKGVER=$(cabal info . | awk '{print $2;exit}') - SRC_TGZ=$PKGVER.tar.gz - cd dist - tar zxfv "$SRC_TGZ" - cd "$PKGVER" - cabal configure --enable-tests --ghc-options -O0 - cabal build - if [ "$CABALVER" = "1.16" ] || [ "$CABALVER" = "1.18" ]; then - cabal test - else - cabal test --show-details=streaming --log=/dev/stdout - fi - cd $ORIGDIR - done - ;; - esac - set +ex - -after_success: -- | - set -ex - if [ "$BUILD" = "stack" ] && [ "$UPLOAD" = "yes" ]; then - sh tools/attach-binary.sh - fi diff --git a/Makefile b/Makefile index a8f5dc2..7c7d08a 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ .PHONY: format test compare ghcid -LOCALINSTALL := $(shell stack $(STACKARGS) path | grep ^local-install-root: | awk '{print $$2}')/bin -DRAW = $(LOCALINSTALL)/drawpuzzle +DRAW ?= $(shell cabal list-bin drawpuzzle) ghcid: ghcid diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..ee33796 --- /dev/null +++ b/flake.lock @@ -0,0 +1,42 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1649676176, + "narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1650974676, + "narHash": "sha256-s1l10UXYd6EIBWm7s3h7oeRFGssv+LOtYqeDDW0gz4Q=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "5f3eeb9c5282b8c0f3cfe3397b8dc500dfc420b4", + "type": "github" + }, + "original": { + "owner": "nixos", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..ca0f47b --- /dev/null +++ b/flake.nix @@ -0,0 +1,80 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem (system: let + pkgs = nixpkgs.legacyPackages.${system}; + t = pkgs.lib.trivial; + hl = pkgs.haskell.lib; + + name = "puzzld"; + + project = devTools: let + addBuildTools = (t.flip hl.addBuildTools) devTools; + in + pkgs.haskellPackages.developPackage { + root = + pkgs.lib.sourceFilesBySuffices ./. + [".cabal" ".hs" "LICENSE" ".svg" ".pzg" ".pzl" ".png" ".sh" "Makefile"]; + name = name; + returnShellEnv = !(devTools == []); + source-overrides = {"SVGFonts" = "1.7.0.1";}; + modifier = (t.flip t.pipe) [ + addBuildTools + ((t.flip hl.addBuildTools) [pkgs.imagemagick]) + hl.dontHaddock + hl.enableStaticLibraries + hl.justStaticExecutables + hl.disableLibraryProfiling + hl.disableExecutableProfiling + ((t.flip hl.overrideCabal) (drv: { + postCheck = '' + patchShebangs tests/examples/gallery.sh + make compare DRAW=$(pwd)/dist/build/drawpuzzle/drawpuzzle + ''; + })) + ]; + }; + in { + packages = rec { + puzzle-draw = project []; + puzzle-draw-web = pkgs.callPackage ./nix/web.nix {}; + puzzle-draw-serve = import ./nix/serve.nix { + inherit pkgs system; + inherit puzzle-draw puzzle-draw-web; + src = self; + }; + }; + defaultPackage = self.packages.${system}.puzzle-draw; + + devShell = project [ + pkgs.haskellPackages.cabal-fmt + pkgs.haskellPackages.cabal-install + ]; + }) + // rec { + nixosModule = import ./nix/module.nix; + nixosConfigurations.test = nixpkgs.lib.nixosSystem rec { + system = "x86_64-linux"; + modules = [ + ({ + config, + pkgs, + ... + }: { + boot.isContainer = true; + services.puzzle-draw.enable = true; + services.puzzle-draw.package = self.packages.${system}.puzzle-draw-serve; + }) + nixosModule + ]; + }; + }; +} diff --git a/nix/module.nix b/nix/module.nix new file mode 100644 index 0000000..7656ca5 --- /dev/null +++ b/nix/module.nix @@ -0,0 +1,67 @@ +{ + config, + lib, + pkgs, + modulesPath, + ... +}: +with lib; let + cfg = config.services.puzzle-draw; +in { + options.services.puzzle-draw = { + enable = mkEnableOption "puzzle-draw server"; + + package = mkOption { + default = pkgs.puzzle-draw-serve; + defaultText = "pkgs.puzzle-draw-serve"; + type = types.package; + description = '' + puzzle-draw-serve package to use. + ''; + }; + + hostName = mkOption { + type = types.nullOr types.str; + default = null; + description = "Hostname to serve puzzle-draw virtual host on (null to disable)"; + }; + + nginx = mkOption { + type = + types.submodule + (import "${modulesPath}/services/web-servers/nginx/vhost-options.nix" { + inherit config lib; + }); + default = {}; + description = "Extra configuration for the nginx virtual host of puzzle-draw."; + }; + }; + + config = mkIf cfg.enable { + systemd.services.puzzle-draw = { + description = "Run puzzle-draw server"; + wantedBy = ["multi-user.target"]; + after = ["networking.target"]; + serviceConfig = { + WorkingDirectory = "${cfg.package}"; + ExecStart = "${cfg.package}/bin/servepuzzle -b 127.0.0.1 -p 8765"; + Restart = "always"; + }; + }; + services.nginx = { + upstreams.puzzle-draw-backend.servers."localhost:8765" = {}; + virtualHosts = mkIf (cfg.hostName != null) { + "${cfg.hostName}" = mkMerge [ + cfg.nginx + { + locations = { + "/" = { + proxyPass = "http://puzzle-draw-backend/"; + }; + }; + } + ]; + }; + }; + }; +} diff --git a/nix/serve.nix b/nix/serve.nix new file mode 100644 index 0000000..5f3edbc --- /dev/null +++ b/nix/serve.nix @@ -0,0 +1,25 @@ +{ + src, + pkgs, + system, + puzzle-draw, + puzzle-draw-web, +}: +derivation { + inherit src system; + name = "puzzle-draw-serve"; + builder = "${pkgs.bash}/bin/bash"; + args = [ + "-c" + '' + PATH=${pkgs.coreutils}/bin:$PATH + mkdir -p $out/bin + cp ${puzzle-draw}/bin/servepuzzle $out/bin/ + mkdir -p $out/static + cp $src/static/* $out/static/ + cp -r $src/tests/examples $out/static/ + cp ${puzzle-draw-web}/web.min.js $out/static/web.js + # post-cp comment because nix is insane: https://github.com/NixOS/nix/issues/2176 + '' + ]; +} diff --git a/nix/web-registry.dat b/nix/web-registry.dat new file mode 100644 index 0000000..e022891 Binary files /dev/null and b/nix/web-registry.dat differ diff --git a/nix/web.nix b/nix/web.nix new file mode 100644 index 0000000..da2f029 --- /dev/null +++ b/nix/web.nix @@ -0,0 +1,62 @@ +{pkgs ? import {}}: let + srcs = { + "elm/browser" = { + sha256 = "1apmvyax93nvmagwj00y16zx10kfv640cxpi64xgqbgy7d2wphy4"; + version = "1.0.0"; + }; + + "elm/core" = { + sha256 = "10kr86h4v5h4p0586q406a5wbl8xvr1jyrf6097zp2wb8sv21ylw"; + version = "1.0.0"; + }; + + "elm/html" = { + sha256 = "1n3gpzmpqqdsldys4ipgyl1zacn0kbpc3g4v3hdpiyfjlgh8bf3k"; + version = "1.0.0"; + }; + + "elm/http" = { + sha256 = "1igmm89ialzrjib1j8xagkxalq1x2gj4l0hfxcd66mpwmvg7psl8"; + version = "1.0.0"; + }; + + "elm/json" = { + sha256 = "1g0hafkqf2q633r7ir9wxpb1lnlzskhpsyi0h5bkzj0gl072zfnb"; + version = "1.0.0"; + }; + + "elm/time" = { + sha256 = "0vch7i86vn0x8b850w1p69vplll1bnbkp8s383z7pinyg94cm2z1"; + version = "1.0.0"; + }; + + "elm/url" = { + sha256 = "0av8x5syid40sgpl5vd7pry2rq0q4pga28b4yykn9gd9v12rs3l4"; + version = "1.0.0"; + }; + + "elm/virtual-dom" = { + sha256 = "0hm8g92h7z39km325dlnhk8n00nlyjkqp3r3jppr37k2k13md6aq"; + version = "1.0.0"; + }; + }; +in + with pkgs; + stdenv.mkDerivation { + src = ../web; + name = "puzzle-draw-web"; + + buildInputs = [elmPackages.elm nodePackages.uglify-js]; + + buildPhase = pkgs.elmPackages.fetchElmDeps { + elmPackages = srcs; + elmVersion = "0.19.1"; + registryDat = ./web-registry.dat; + }; + + installPhase = '' + elm make src/Main.elm --output $out/web.js + uglifyjs $out/web.js --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \ + | uglifyjs --mangle --output $out/web.min.js + ''; + } diff --git a/puzzle-draw.cabal b/puzzle-draw.cabal index dcc0cc5..41e0c10 100644 --- a/puzzle-draw.cabal +++ b/puzzle-draw.cabal @@ -67,14 +67,14 @@ library diagrams-lib >=1.4.2.3 && <1.5, parsec >=3.1, yaml >=0.8.4, - aeson >=0.7, + aeson >=2.0, unordered-containers >=0.2, containers >=0.5, hashable >=1.2, bytestring >=0.10, text >=1.1, svg-builder >=0.1, - SVGFonts >=1.7, + SVGFonts >=1.7 && <1.8, vector-space >=0.8, mtl >=2.1, optparse-applicative >=0.13, @@ -97,7 +97,7 @@ executable drawpuzzle diagrams-lib >=1.4.2.3 && <1.5, yaml >=0.8.4, optparse-applicative >=0.12, - aeson >=0.7, + aeson >=2.0, filepath >=1.3, bytestring >=0.10, diagrams-rasterific >=1.4.2, @@ -113,7 +113,7 @@ executable checkpuzzle yaml >=0.8.4, containers >=0.5, optparse-applicative >=0.12, - aeson >=0.7, + aeson >=2.0, bytestring >=0.10, filepath >=1.3 @@ -128,7 +128,7 @@ executable servepuzzle snap-core >=0.9, snap-server >=0.9, diagrams-lib >=1.2, - aeson >=0.7, + aeson >=2.0, yaml >=0.8.4, filepath >=1.3, directory >=1.2, diff --git a/src/Data/GridShape.hs b/src/Data/GridShape.hs index b1467ed..f49a41d 100644 --- a/src/Data/GridShape.hs +++ b/src/Data/GridShape.hs @@ -42,6 +42,7 @@ where import Data.AffineSpace import qualified Data.Foldable as F +import Data.Kind (Type) import Data.List ( groupBy, partition, @@ -158,7 +159,7 @@ edge p q = unorient $ edge' p q class Dual2D a where - type Dual a :: * + type Dual a :: Type dualE' :: Edge' a -> Edge' (Dual a) diff --git a/src/Parse/Util.hs b/src/Parse/Util.hs index e2f0708..5731906 100644 --- a/src/Parse/Util.hs +++ b/src/Parse/Util.hs @@ -32,6 +32,7 @@ import Data.Ord (comparing) import qualified Data.Text as T import Data.Traversable (mapM) import Data.Yaml +import qualified Data.Aeson.Key import Parse.Parsec import Text.Read (readMaybe) import Prelude hiding (mapM) @@ -39,11 +40,11 @@ import Prelude hiding (mapM) type Path = [String] field :: Path -> Value -> Parser Value -field = field' . map T.pack +field = field' . map (Data.Aeson.Key.fromText . T.pack) where field' [] v = pure v field' (f : fs) (Object v) = v .: f >>= field' fs - field' (f : _) _ = fail $ "expected field '" ++ T.unpack f ++ "'" + field' (f : _) _ = fail $ "expected field '" ++ (T.unpack . Data.Aeson.Key.toText $ f) ++ "'" parseFrom :: Path -> (Value -> Parser b) -> Value -> Parser b parseFrom fs p v = diff --git a/tests/examples/gallery.sh b/tests/examples/gallery.sh index 99780b8..3c93079 100755 --- a/tests/examples/gallery.sh +++ b/tests/examples/gallery.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu diff --git a/tests/examples/greaterwall.png b/tests/examples/greaterwall.png index e700e35..8450817 100644 Binary files a/tests/examples/greaterwall.png and b/tests/examples/greaterwall.png differ diff --git a/web/elm.json b/web/elm.json index 7617af7..18183a6 100644 --- a/web/elm.json +++ b/web/elm.json @@ -3,7 +3,7 @@ "source-directories": [ "src" ], - "elm-version": "0.19.0", + "elm-version": "0.19.1", "dependencies": { "direct": { "elm/browser": "1.0.0",