on master help sign in
Merge branch 'develop' into alexfmpe-patch-1
commit9adc07e83f756319bced8c83267f5404306abd7c authorElliot Cameron <3noch@users.noreply.github.com> dateWed 27 May 2020, 10:09 (6 years ago) parenta6000a7d25bdd4
27 files changed+244−138
ChangeLog.md+11−1 default.nix+16−12 dep/.gitignore−5 dep/reflex-platform/default.nix+1−7 dep/reflex-platform/github.json+3−3 dep/reflex-platform/thunk.nix+9 guides/app-deploy/README.md+14−8 haskell-overlays/obelisk.nix+1 lib/asset/manifest/obelisk-asset-manifest.cabal+1−1 lib/asset/manifest/src/Obelisk/Asset/Symlink.hs+1−1 lib/asset/serve-snap/obelisk-asset-serve-snap.cabal+1−1 lib/asset/serve-snap/src/Obelisk/Asset/Serve/Snap.hs+1−1 lib/cliapp/obelisk-cliapp.cabal+2 lib/cliapp/src/Obelisk/CliApp.hs+1 lib/cliapp/src/Obelisk/CliApp/Process.hs+4−2 lib/command/obelisk-command.cabal+3−1 lib/command/src/Obelisk/Command.hs+12−8 lib/command/src/Obelisk/Command/Deploy.hs+1−1 lib/command/src/Obelisk/Command/Project.hs+14−7 lib/command/src/Obelisk/Command/Run.hs+57−45 lib/command/src/Obelisk/Command/Thunk.hs+3−3 lib/command/src/Obelisk/Command/Utils.hs+6 lib/frontend/src/Obelisk/Frontend.hs+1−1 lib/route/src/Obelisk/Route.hs+70−27 lib/selftest/src/Obelisk/SelfTest.hs+7 lib/snap-extras/obelisk-snap-extras.cabal−1 skeleton/frontend/frontend.cabal+4−2
ChangeLog.md+11−1
@@ -2,9 +2,19 @@
22
33This project's release branch is `master`. This log is written from the perspective of the release branch: when changes hit `master`, they are considered released.
44
5+## Unreleased
6+
7+* ([#715](https://github.com/obsidiansystems/obelisk/pull/715)) In `Obelisk.Route` deprecate `isoEncoder` and `prismEncoder` in favor of more precisely named `viewEncoder` and `reviewEncoder` (respectively) and improve documentation regarding contravariance of `reviewEncoder`.
8+* ([#739](https://github.com/obsidiansystems/obelisk/pull/739)) Improve `ob shell` by allowing commands to be passed verbatim after a `--` argument. For example, `ob shell 'run command'` can now be written `ob shell -- run command`.
9+* ([#735](https://github.com/obsidiansystems/obelisk/pull/735)) Fix regression causing custom `Prelude`s to break `ob run`/`ob watch`/`ob repl`.
10+* ([#737](https://github.com/obsidiansystems/obelisk/pull/737)) Fix bug causing custom `Prelude`s to break `ob profile`.
11+* ([#742](https://github.com/obsidiansystems/obelisk/pull/742), [#57](https://github.com/obsidiansystems/obelisk/pull/57), [#406](https://github.com/obsidiansystems/obelisk/pull/406)) Enable `-dedupe` and `-DGHCJS_BROWSER` in GHCJS builds to make JavaScript output considerably smaller leading to faster load/parse times and faster build times.
12+* ([#742](https://github.com/obsidiansystems/obelisk/pull/742)) Update `reflex-platform` which includes:
13+ * A new version of GHCJS where `-dedupe` is fixed.
14+
515## v0.8.0.0
616
7−* ([#674](https://github.com/obsidiansystems/obelisk/pull/674), [#711](https://github.com/obsidiansystems/obelisk/pull/711)) Introduce a new thunk format to support accessing the thunk's source directly when packed. When packed, thunks have an additional file called `thunk.nix` and `default.nix` is now a think wrapper around that.
17+* ([#674](https://github.com/obsidiansystems/obelisk/pull/674), [#711](https://github.com/obsidiansystems/obelisk/pull/711)) Introduce a new thunk format to support accessing the thunk's source directly when packed. When packed, thunks have an additional file called `thunk.nix` and `default.nix` is now a thin wrapper around that.
818* ([#665](https://github.com/obsidiansystems/obelisk/pull/665)) Add `--interpret` and `--no-interpret` options to `ob run`/`ob watch`/`ob repl`/`ob shell`. These options allow you to pick which paths will be pre-compiled by `nix` when entering the shell/session and which won't. For example `ob run --no-interpret dep` will ensure that any dependencies found in `./dep` will be built by `nix` before loading the rest of the project into the `ghci` session. The same configuration for `ob shell` will ensure that those packages are built and available in the `ghc` package database inside the shell.
919
1020 **NOTE:** `ob shell`'s default behavior is now different. By default it now behaves like `ob run`/`ob watch`/`ob repl` in that it does *not* pre-build any packages whose `.cabal` or `package.yaml` files are found in the project. To regain the previous behavior, use `ob shell --no-interpret . --interpret backend --interpret common --interpret frontend` from the project root.
default.nix+16−12
@@ -321,29 +321,33 @@ in rec {
321321 __unstable__.profiledObRun = let
322322 profiled = projectOut { inherit system; enableLibraryProfiling = true; };
323323 exeSource = builtins.toFile "ob-run.hs" ''
324+ {-# LANGUAGE NoImplicitPrelude #-}
325+ {-# LANGUAGE PackageImports #-}
324326 module Main where
325327
326− import Control.Exception
327− import Reflex.Profiled
328− import System.Environment
328+ -- Explicitly import Prelude from base lest there be multiple modules called Prelude
329+ import "base" Prelude (IO, (++), read)
329330
330− import qualified Obelisk.Run
331+ import "base" Control.Exception (finally)
332+ import "reflex" Reflex.Profiled (writeProfilingData)
333+ import "base" System.Environment (getArgs)
334+
335+ import qualified "obelisk-run" Obelisk.Run
331336 import qualified Frontend
332337 import qualified Backend
333338
334339 main :: IO ()
335340 main = do
336− args <- getArgs
337− let port = read $ args !! 0
338− assets = args !! 1
339− profileFile = (args !! 2) <> ".rprof"
340− Obelisk.Run.run port (Obelisk.Run.runServeAsset assets) Backend.backend Frontend.frontend `finally` writeProfilingData profileFile
341+ [portStr, assets, profFileName] <- getArgs
342+ Obelisk.Run.run (read portStr) (Obelisk.Run.runServeAsset assets) Backend.backend Frontend.frontend
343+ `finally` writeProfilingData (profFileName ++ ".rprof")
341344 '';
342345 in nixpkgs.runCommand "ob-run" {
343− buildInputs = [ (profiled.ghc.ghcWithPackages (p: [ p.backend p.frontend])) ];
346+ buildInputs = [ (profiled.ghc.ghcWithPackages (p: [p.backend p.frontend])) ];
344347 } ''
345− mkdir -p $out/bin/
346− ghc -x hs -prof -fno-prof-auto -threaded ${exeSource} -o $out/bin/ob-run
348+ cp ${exeSource} ob-run.hs
349+ mkdir -p $out/bin
350+ ghc -x hs -prof -fno-prof-auto -threaded ob-run.hs -o $out/bin/ob-run
347351 '';
348352
349353 linuxExeConfigurable = linuxExe;
dep/.gitignoredeleted−5
@@ -1,5 +0,0 @@
1−*
2−!*/
3−!.gitignore
4−!*/*.json
5−!*/default.nix
dep/reflex-platform/default.nix+1−7
@@ -1,8 +1,2 @@
11# DO NOT HAND-EDIT THIS FILE
2−let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
3− if !fetchSubmodules && !private then builtins.fetchTarball {
4− url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
5− } else (import <nixpkgs> {}).fetchFromGitHub {
6− inherit owner repo rev sha256 fetchSubmodules private;
7− };
8−in import (fetch (builtins.fromJSON (builtins.readFile ./github.json)))
2+import (import ./thunk.nix)
dep/reflex-platform/github.json+3−3
@@ -1,8 +1,8 @@
11{
22 "owner": "reflex-frp",
33 "repo": "reflex-platform",
4− "branch": "release/0.5.3.0",
4+ "branch": "develop",
55 "private": false,
6− "rev": "9779ea3b4c87fdc971057c561262696d5e9b1847",
7− "sha256": "0v87ilal9355xwz8y9m0zh14pm9c0f7pqch0854kkj92ybc5l62q"
6+ "rev": "c21ec7036c8351185cd2feef9ce577b6a61c4f22",
7+ "sha256": "0m4sdpz6qvngralpyprrbvsyi2zmfh2l3q5vhihwfp3842blvbfg"
88}
dep/reflex-platform/thunk.nixnew file+9
@@ -0,0 +1,9 @@
1+# DO NOT HAND-EDIT THIS FILE
2+let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }:
3+ if !fetchSubmodules && !private then builtins.fetchTarball {
4+ url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256;
5+ } else (import <nixpkgs> {}).fetchFromGitHub {
6+ inherit owner repo rev sha256 fetchSubmodules private;
7+ };
8+ json = builtins.fromJSON (builtins.readFile ./github.json);
9+in fetch json
guides/app-deploy/README.md+14−8
@@ -1,11 +1,12 @@
11# Obelisk Application Deployment Guide
2+
23This document serves two purposes, the first one is to guide new users that want to deploy Obelisk/Reflex apps and the second one to provide a walk-through for test driving Obelisk releases.
34
4−> **Note:** To complete this *entire* guide you need access to a macOS machine running the latest OSX release, a Linux machine (can be a VM on that mac) with Nix installed, an iPhone or iPad, and a recent Android device. It also assumes that you have Nix correctly set up and that your system is set up to fetch from binary caches instead of building everything on your machine. If you notice any of the commands below takes more than 5 minutes, then caches are not likely enabled.
5+> **Note:** This guide can be completed on a Linux host with Nix installed or a macOS host with Nix installed. Each section is labeled regarding which operating system hosts it supports. When going through the guide, start at the beginning and go through all sections that are supported on your host and merely skip sections that are not supported. The sections for iOS deployments require an iPhone or iPad device and the sections for Android deployments require an Android device. This guide assumes that you have Nix correctly set up and that your system is configured to fetch from binary caches instead of building everything on your machine. If you notice any of the commands below takes more than 5 minutes, then caches are not likely enabled.
56>
67> Refer to the [Obelisk Installation Documentation](https://github.com/obsidiansystems/obelisk#installing-obelisk) for instructions on configuring the binary caches for any Nix-compatible setup.
78
8−## a) Get Obelisk
9+## a) Get Obelisk (Linux, Mac)
910
1011There are several ways to install Obelisk, but the one we are going to use here allows us to test a single version of Obelisk in different ways. The approach is to clone Obelisk from it's GitHub repository and start a project that uses that local check out.
1112
@@ -39,7 +40,7 @@ ob init --branch "$OBELISK_BRANCH"
3940
4041> **Note:** If the `--branch` parameter is not used, then Obelisk sets your project up to look at the `master` branch from GitHub. This is quite handy for real life projects but distracting for our test drive since we want to make sure any changes we do to the Obelisk codebase (like testing a pull request) are immediately picked up.
4142
42−## b) Run the Obelisk app on localhost
43+## b) Run the Obelisk app on localhost (Linux, Mac)
4344
4445Let's test that we can run a server on localhost. This is quite easy:
4546
@@ -52,9 +53,10 @@ Now open a browser and point it to http://localhost:8000 (or just click on this
5253
5354![](assets/app-deploy.png)
5455
55−## c) Deploy the web app on a remote machine
56+## c) Prepare a VirtualBox deploy target (Linux, Mac)
5657
5758### Import the NixOS VirtualBox appliance
59+
5860Install [VirtualBox](https://www.virtualbox.org/) on your machine.
5961
6062On NixOS, do this by adding the following line to your `/etc/nixos/configuration.nix` before the last line containing `}`:
@@ -201,7 +203,7 @@ git remote add origin "$WORKDIR/myapp-git-remote"
201203git push -u origin master
202204```
203205
204−### Deploy
206+### Initialize deployment directory
205207
206208With that, we can come back to the Obelisk app and initialize the deployment:
207209
@@ -216,7 +218,11 @@ Then configure the deployment for VirtualBox:
216218echo '{nixosPkgs, ...}: {...}: { imports = [ (nixosPkgs.path + /nixos/modules/virtualisation/virtualbox-image.nix) ]; }' > "$WORKDIR/myappdeploy/module.nix"
217219```
218220
219−And deploy:
221+## d) Deploy to VirtualBox target (Linux only)
222+
223+> Deploying requires a Linux host in order to build the Linux binaries.
224+
225+Now we can deploy to the VirtualBox VM.
220226
221227```bash
222228cd "$WORKDIR/myappdeploy"
@@ -243,7 +249,7 @@ It should look like this:
243249
244250Congratulations! You have deployed an Obelisk application to a remote server via SSH.
245251
246−## d) Deploy an Android app (Linux host only)
252+## e) Deploy an Android app (Linux only)
247253
248254> **Note:** This section requires that you be running a Linux *host*.
249255
@@ -286,7 +292,7 @@ When the deployment is complete opening the app should look something like
286292
287293Congratulations! You have deployed an Obelisk Android app via USB.
288294
289−## e) Deploy an iOS app (macOS host only)
295+## f) Deploy an iOS app (Mac only)
290296
291297> **Note:** This section requires that you be running a macOS *host*.
292298
haskell-overlays/obelisk.nix+1
@@ -20,6 +20,7 @@ in
2020 librarySystemDepends = [
2121 pkgs.jre
2222 pkgs.nix
23+ pkgs.nix-prefetch-git
2324 (haskellLib.justStaticExecutables self.ghcid)
2425 ];
2526 };
lib/asset/manifest/obelisk-asset-manifest.cabal+1−1
@@ -24,7 +24,7 @@ library
2424 , template-haskell
2525 , text
2626 , transformers
27− , unix
27+ , unix-compat
2828 , vector
2929 exposed-modules:
3030 Obelisk.Asset.Gather
lib/asset/manifest/src/Obelisk/Asset/Symlink.hs+1−1
@@ -7,7 +7,7 @@ import Data.Map (Map)
77import qualified Data.Map as Map
88import System.Directory
99import System.FilePath.Posix
10−import System.Posix.Files
10+import System.PosixCompat.Files
1111
1212-- | Copy the given hashed files from one location to hashed paths in another
1313-- location, and symlink the unhashed paths to the hashed ones
lib/asset/serve-snap/obelisk-asset-serve-snap.cabal+1−1
@@ -23,7 +23,7 @@ library
2323 , obelisk-snap-extras
2424 , text
2525 , transformers
26− , unix
26+ , unix-compat
2727 exposed-modules:
2828 Obelisk.Asset.Accept
2929 Obelisk.Asset.Serve.Snap
lib/asset/serve-snap/src/Obelisk/Asset/Serve/Snap.hs+1−1
@@ -31,7 +31,7 @@ import Data.Text.Encoding (decodeUtf8, encodeUtf8)
3131import System.Directory (getDirectoryContents)
3232import System.FilePath ((</>), splitFileName, takeDirectory)
3333import System.IO.Error (isDoesNotExistError)
34−import System.Posix (getFileStatus, fileSize)
34+import System.PosixCompat.Files (getFileStatus, fileSize)
3535
3636-- | Serve static assets from an asset directory generated via @assets.nix@ or, failing that, from a regular directory.
3737--
lib/cliapp/obelisk-cliapp.cabal+2
@@ -19,10 +19,12 @@ library
1919 , monad-loops
2020 , mtl
2121 , process
22+ , shell-escape
2223 , terminal-size
2324 , text
2425 , time
2526 , transformers
27+ , utf8-string
2628 exposed-modules:
2729 Obelisk.CliApp
2830 other-modules:
lib/cliapp/src/Obelisk/CliApp.hs+1
@@ -39,6 +39,7 @@ module Obelisk.CliApp
3939 , callCommand
4040 , callProcess
4141 , callProcessAndLogOutput
42+ , createProcess
4243 , createProcess_
4344 , overCreateProcess
4445 , proc
lib/cliapp/src/Obelisk/CliApp/Process.hs+4−2
@@ -38,6 +38,7 @@ import Control.Monad.IO.Class (MonadIO, liftIO)
3838import Control.Lens (Prism', review)
3939import qualified Data.ByteString as BS
4040import qualified Data.ByteString.Char8 as BSC
41+import qualified Data.ByteString.UTF8 as BSU
4142import Data.Function (fix)
4243import Data.Map (Map)
4344import qualified Data.Map as Map
@@ -54,6 +55,7 @@ import qualified System.IO.Streams as Streams
5455import System.IO.Streams.Concurrent (concurrentMerge)
5556import System.Process (CreateProcess, ProcessHandle, StdStream (CreatePipe), std_err, std_out)
5657import qualified System.Process as Process
58+import Text.ShellEscape (bash, bytes)
5759import qualified Data.Aeson as Aeson
5860
5961import Control.Monad.Log (Severity (..))
@@ -246,8 +248,8 @@ reconstructCommand p = case p of
246248 Process.ShellCommand str -> T.pack str
247249 Process.RawCommand c as -> processToShellString c as
248250 where
249− processToShellString cmd args = T.unwords $ map quoteAndEscape (cmd : args)
250− quoteAndEscape x = "'" <> T.replace "'" "'\''" (T.pack x) <> "'"
251+ processToShellString cmd args = T.pack $ unwords $
252+ map (BSU.toString . bytes . bash . BSU.fromString) (cmd : args)
251253
252254reconstructProcSpec :: ProcessSpec -> Text
253255reconstructProcSpec = reconstructCommand . Process.cmdspec . _processSpec_createProcess
lib/command/obelisk-command.cabal+3−1
@@ -39,13 +39,15 @@ library
3939 , optparse-applicative
4040 , placeholders
4141 , process
42+ , shell-escape
4243 , temporary
4344 , terminal-size
4445 , text
4546 , time
4647 , transformers
47− , unix
48+ , unix-compat
4849 , unordered-containers
50+ , utf8-string
4951 , which
5052 , yaml
5153 exposed-modules:
lib/command/src/Obelisk/Command.hs+12−8
@@ -22,7 +22,7 @@ import System.Environment
2222import System.FilePath
2323import qualified System.Info
2424import System.IO (hIsTerminalDevice, stdout)
25−import System.Posix.Process (executeFile)
25+import System.Process (rawSystem)
2626
2727import Obelisk.App
2828import Obelisk.CliApp
@@ -114,7 +114,7 @@ obCommand cfg = hsubparser
114114 , command "thunk" $ info (ObCommand_Thunk <$> thunkOption) $ progDesc "Manipulate thunk directories"
115115 , command "repl" $ info (ObCommand_Repl <$> interpretOpts) $ progDesc "Open an interactive interpreter"
116116 , command "watch" $ info (ObCommand_Watch <$> interpretOpts) $ progDesc "Watch current project for errors and warnings"
117− , command "shell" $ info (ObCommand_Shell <$> shellOpts) $ progDesc "Enter a shell with project dependencies"
117+ , command "shell" $ info (ObCommand_Shell <$> shellOpts) $ progDesc "Enter a shell with project dependencies or run a command in such a shell. E.g. ob shell -- ghc-pkg list"
118118 , command "doc" $ info (ObCommand_Doc <$> shellFlags <*> packageNames) $
119119 progDesc "List paths to haddock documentation for specified packages"
120120 <> footerDoc (Just $
@@ -285,7 +285,10 @@ shellOpts :: Parser ShellOpts
285285shellOpts = ShellOpts
286286 <$> shellFlags
287287 <*> interpretOpts
288− <*> optional (strArgument (metavar "COMMAND"))
288+ -- This funny construction is used to support optparse-applicative's @--@ parsing.
289+ -- All arguments after @--@ are left unparsed and instead provided to the last positional parser
290+ -- which must therefore be 'many' in order to consume the rest of the input.
291+ <*> ((\xs -> if null xs then Nothing else Just $ unwords xs) <$> many (strArgument (metavar "COMMAND")))
289292
290293portOpt :: Int -> Parser Int
291294portOpt dfault = option auto (long "port" <> short 'p' <> help "Port number for server" <> showDefault <> value dfault <> metavar "INT")
@@ -363,7 +366,8 @@ main' argsCfg = do
363366 Just impl -> do
364367 -- Invoke the real implementation, using --no-handoff to prevent infinite recursion
365368 putLog Debug $ "Handing off to " <> T.pack impl
366− liftIO $ executeFile impl False ("--no-handoff" : myArgs) Nothing
369+ _ <- liftIO $ rawSystem impl ("--no-handoff" : myArgs)
370+ return ()
367371 case myArgs of
368372 "--no-handoff" : as -> go as -- If we've been told not to hand off, don't hand off
369373 origPath:inPath:outPath:preprocessorName:packagePaths
@@ -398,11 +402,11 @@ ob = \case
398402 ObCommand_Repl interpretPathsList -> withInterpretPaths interpretPathsList runRepl
399403 ObCommand_Watch interpretPathsList -> withInterpretPaths interpretPathsList runWatch
400404 ObCommand_Shell (ShellOpts shellAttr interpretPathsList cmd) -> withInterpretPaths interpretPathsList $ \root interpretPaths ->
401− nixShellForInterpretPaths False shellAttr root interpretPaths cmd
402− ObCommand_Doc shellAttr pkgs -> withProjectRoot "." $ \root ->
403− nixShellForInterpretPaths True shellAttr root emptyPathTree $ Just $ haddockCommand pkgs
405+ nixShellForInterpretPaths False shellAttr root interpretPaths cmd -- N.B. We do NOT bash escape here; we want to run the command as-is
406+ ObCommand_Doc shellAttr pkgs -> withInterpretPaths [] $ \root interpretPaths ->
407+ nixShellForInterpretPaths True shellAttr root interpretPaths $ Just $ haddockCommand pkgs
404408 ObCommand_Hoogle shell' port -> withProjectRoot "." $ \root -> do
405− nixShellWithHoogle root True shell' $ Just $ "hoogle server -p " <> show port <> " --local"
409+ nixShellWithHoogle root True shell' $ Just $ "hoogle server -p" <> show port <> " --local"
406410 ObCommand_Internal icmd -> case icmd of
407411 ObInternal_ApplyPackages origPath inPath outPath packagePaths -> do
408412 liftIO $ Preprocessor.applyPackages origPath inPath outPath packagePaths
lib/command/src/Obelisk/Command/Deploy.hs+1−1
@@ -25,7 +25,7 @@ import GHC.Generics
2525import System.Directory
2626import System.FilePath
2727import System.IO
28−import System.Posix.Files
28+import System.PosixCompat.Files
2929import Text.URI (URI)
3030import qualified Text.URI as URI
3131import Text.URI.Lens
lib/command/src/Obelisk/Command/Project.hs+14−7
@@ -15,6 +15,7 @@ module Obelisk.Command.Project
1515 , toImplDir
1616 , toObeliskDir
1717 , withProjectRoot
18+ , bashEscape
1819 ) where
1920
2021import Control.Concurrent.MVar (MVar, newMVar, withMVarMasked)
@@ -23,6 +24,7 @@ import Control.Monad
2324import Control.Monad.IO.Class (liftIO)
2425import Control.Monad.State
2526import qualified Data.Aeson as Json
27+import qualified Data.ByteString.UTF8 as BSU
2628import Data.Bits
2729import qualified Data.ByteString.Lazy as BSL
2830import Data.Default (def)
@@ -30,15 +32,17 @@ import Data.Function ((&), on)
3032import Data.Map (Map)
3133import Data.Text (Text)
3234import qualified Data.Text as T
33−import Data.Text.Encoding (decodeUtf8)
35+import Data.Text.Encoding (decodeUtf8, encodeUtf8)
3436import Data.Traversable (for)
3537import System.Directory
3638import System.Environment (lookupEnv)
3739import System.FilePath
3840import System.IO.Temp
3941import System.IO.Unsafe (unsafePerformIO)
40−import System.Posix (FileStatus, FileMode, CMode (..), UserID, deviceID, fileID, fileMode, fileOwner, getFileStatus, getRealUserID)
41−import System.Posix.Files
42+import System.PosixCompat.Files
43+import System.PosixCompat.Types
44+import System.PosixCompat.User
45+import Text.ShellEscape (bash, bytes)
4246
4347import GitHub.Data.GitData (Branch)
4448import GitHub.Data.Name (Name)
@@ -278,12 +282,15 @@ nixShellRunConfig root isPure command = do
278282 pure $ def
279283 & nixShellConfig_pure .~ isPure
280284 & nixShellConfig_common . nixCmdConfig_target .~ (def & target_path .~ Nothing)
281− & nixShellConfig_run .~ (command <&> \c -> mconcat
282− [ "export NIX_PATH=nixpkgs=", T.unpack nixpkgsPath, "; "
283− , maybe "" (\v -> "export NIX_REMOTE=" <> v <> "; ") nixRemote
284− , c
285+ & nixShellConfig_run .~ (command <&> \cs -> unwords $ concat
286+ [ ["export", BSU.toString . bytes . bash $ "NIX_PATH=nixpkgs=" <> encodeUtf8 nixpkgsPath, ";"]
287+ , maybe [] (\v -> ["export", BSU.toString . bytes . bash $ "NIX_REMOTE=" <> encodeUtf8 (T.pack v), ";"]) nixRemote
288+ , [cs]
285289 ])
286290
291+bashEscape :: String -> String
292+bashEscape = BSU.toString . bytes . bash . BSU.fromString
293+
287294nixShellRunProc :: NixShellConfig -> ProcessSpec
288295nixShellRunProc cfg = setDelegateCtlc True $ proc "nix-shell" $ runNixShellConfig cfg
289296
lib/command/src/Obelisk/Command/Run.hs+57−45
@@ -25,12 +25,14 @@ import Data.Default (def)
2525import Data.Either (partitionEithers)
2626import Data.Foldable (fold, for_, toList)
2727import Data.Functor.Identity (runIdentity)
28+import Data.List (intercalate)
2829import Data.List.NonEmpty (NonEmpty)
2930import qualified Data.List.NonEmpty as NE
3031import Data.Map (Map)
3132import qualified Data.Map as Map
3233import qualified Data.Map.Monoidal as MMap
33−import Data.Maybe
34+import Data.Maybe (catMaybes, fromMaybe, mapMaybe)
35+import Data.Ord (comparing)
3436import Data.Set (Set)
3537import qualified Data.Set as Set
3638import Data.String.Here.Interpolated (i)
@@ -44,17 +46,17 @@ import Distribution.Compiler (CompilerFlavor(..))
4446import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
4547import Distribution.Parsec.ParseResult (runParseResult)
4648import qualified Distribution.System as Dist
47−import Distribution.Types.BuildInfo
48−import Distribution.Types.CondTree
49−import Distribution.Types.GenericPackageDescription
50−import Distribution.Types.Library
51−import Distribution.Utils.Generic
49+import Distribution.Types.BuildInfo (buildable, defaultExtensions, defaultLanguage, hsSourceDirs, options)
50+import Distribution.Types.CondTree (simplifyCondTree)
51+import Distribution.Types.GenericPackageDescription (ConfVar (Arch, Impl, OS), condLibrary)
52+import Distribution.Types.Library (libBuildInfo)
53+import Distribution.Utils.Generic (toUTF8BS, readUTF8File)
5254import qualified Distribution.Parsec.Common as Dist
5355import qualified Hpack.Config as Hpack
5456import qualified Hpack.Render as Hpack
5557import qualified Hpack.Yaml as Hpack
56−import Language.Haskell.Extension
57−import Network.Socket hiding (Debug)
58+import Language.Haskell.Extension (Extension, Language)
59+import qualified Network.Socket as Socket
5860import System.Directory
5961import System.Environment (getExecutablePath)
6062import System.FilePath
@@ -76,7 +78,7 @@ import Obelisk.CliApp (
7678 withSpinner,
7779 )
7880import Obelisk.Command.Nix
79−import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets)
81+import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets, bashEscape)
8082import Obelisk.Command.Thunk (attrCacheFileName)
8183import Obelisk.Command.Utils (findExePath, ghcidExePath)
8284
@@ -109,9 +111,6 @@ data PathTree a = PathTree_Node
109111 (Map FilePath (PathTree a)) -- Branches to deeper leaves
110112 deriving (Eq, Ord, Show, Functor, Foldable, Traversable)
111113
112−emptyPathTree :: PathTree a
113−emptyPathTree = PathTree_Node Nothing mempty
114−
115114-- | 2D ASCII drawing of a 'PathTree'. Adapted from Data.Tree.draw.
116115drawPathTree :: (a -> Text) -> PathTree a -> Text
117116drawPathTree showA (PathTree_Node _ ts0) = T.intercalate "\n" $ goForest (Map.toList ts0)
@@ -164,12 +163,12 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
164163run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
165164run root interpretPaths = do
166165 pkgs <- getParsedLocalPkgs root interpretPaths
167− withGhciScript pkgs $ \dotGhciPath -> do
168− assets <- findProjectAssets root
169− putLog Debug $ "Assets impurely loaded from: " <> assets
170− ghciArgs <- getGhciSessionSettings pkgs root True
171− freePort <- getFreePort
172− runGhcid root True (ghciArgs <> mkGhciScriptArg dotGhciPath) pkgs $ Just $ unwords
166+ assets <- findProjectAssets root
167+ putLog Debug $ "Assets impurely loaded from: " <> assets
168+ ghciArgs <- getGhciSessionSettings pkgs root True
169+ freePort <- getFreePort
170+ withGhciScriptArgs pkgs $ \dotGhciArgs -> do
171+ runGhcid root True (ghciArgs <> dotGhciArgs) pkgs $ Just $ unwords
173172 [ "Obelisk.Run.run"
174173 , show freePort
175174 , "(Obelisk.Run.runServeAsset " ++ show assets ++ ")"
@@ -181,15 +180,15 @@ runRepl :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
181180runRepl root interpretPaths = do
182181 pkgs <- getParsedLocalPkgs root interpretPaths
183182 ghciArgs <- getGhciSessionSettings pkgs "." True
184− withGhciScript pkgs $ \dotGhciPath ->
185− runGhciRepl root pkgs (ghciArgs <> mkGhciScriptArg dotGhciPath)
183+ withGhciScriptArgs pkgs $ \dotGhciArgs ->
184+ runGhciRepl root pkgs (ghciArgs <> dotGhciArgs)
186185
187186runWatch :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
188187runWatch root interpretPaths = do
189188 pkgs <- getParsedLocalPkgs root interpretPaths
190189 ghciArgs <- getGhciSessionSettings pkgs root True
191− withGhciScript pkgs $ \dotGhciPath ->
192− runGhcid root True (ghciArgs <> mkGhciScriptArg dotGhciPath) pkgs Nothing
190+ withGhciScriptArgs pkgs $ \dotGhciArgs ->
191+ runGhcid root True (ghciArgs <> dotGhciArgs) pkgs Nothing
193192
194193exportGhciConfig :: MonadObelisk m => Bool -> FilePath -> PathTree Interpret -> m [String]
195194exportGhciConfig useRelativePaths root interpretPaths = do
@@ -396,13 +395,29 @@ parsePackagesOrFail dirs' = do
396395packageInfoToNamePathMap :: Foldable f => f CabalPackageInfo -> Map Text FilePath
397396packageInfoToNamePathMap = Map.fromList . map (_cabalPackageInfo_packageName &&& _cabalPackageInfo_packageRoot) . toList
398397
398+-- Like 'withGhciScript' but provides the precise ghci arguments to add to a ghci session
399+withGhciScriptArgs
400+ :: (MonadObelisk m, Foldable f)
401+ => f CabalPackageInfo -- ^ List of packages to load into ghci
402+ -> ([String] -> m ()) -- ^ Action to run with the extra ghci arguments
403+ -> m ()
404+withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp -> f ["-XNoImplicitPrelude", "-ghci-script", fp]
405+ where
406+ -- These lines must be first and allow the session to support a custom Prelude when @-XNoImplicitPrelude@
407+ -- is passed to the ghci session.
408+ loadPreludeManually =
409+ [ ":add Prelude" -- @:add@ is used because it's less noisy when there is no custom Prelude
410+ , ":set -XImplicitPrelude" -- Turn the default setting on
411+ ]
412+
399413-- | Create ghci configuration to load the given packages
400414withGhciScript
401415 :: (MonadObelisk m, Foldable f)
402− => f CabalPackageInfo -- ^ List of packages to load into ghci
416+ => [String] -- ^ Commands to prefix to file
417+ -> f CabalPackageInfo -- ^ List of packages to load into ghci
403418 -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporary .ghci
404419 -> m ()
405−withGhciScript (toList -> packageInfos) f =
420+withGhciScript preCommands (toList -> packageInfos) f =
406421 withSystemTempDirectory "ob-ghci" $ \fp -> do
407422 let dotGhciPath = fp </> ".ghci"
408423 liftIO $ writeFile dotGhciPath dotGhci
@@ -414,7 +429,8 @@ withGhciScript (toList -> packageInfos) f =
414429 , [ "Backend" | "backend" `Set.member` packageNames ]
415430 , [ "Frontend" | "frontend" `Set.member` packageNames ]
416431 ]
417− dotGhci = unlines
432+ dotGhci = unlines $
433+ preCommands <>
418434 [ if null modulesToLoad then "" else ":load " <> unwords modulesToLoad
419435 , "import qualified Obelisk.Run"
420436 , "import qualified Frontend"
@@ -467,7 +483,7 @@ runGhciRepl root (toList -> packages) ghciArgs =
467483 -- NOTE: We do *not* want to use $(staticWhich "ghci") here because we need the
468484 -- ghc that is provided by the shell in the user's project.
469485 nixShellWithoutPkgs root True False (packageInfoToNamePathMap packages) "ghc" $
470− Just $ unwords $ "ghci" : ghciArgs -- TODO: Shell escape
486+ Just $ unwords $ fmap bashEscape $ "ghci" : ghciArgs
471487
472488-- | Run ghcid
473489runGhcid
@@ -480,32 +496,28 @@ runGhcid
480496 -> m ()
481497runGhcid root chdirToRoot ghciArgs (toList -> packages) mcmd =
482498 nixShellWithoutPkgs root True chdirToRoot (packageInfoToNamePathMap packages) "ghc" $
483− Just $ unwords $ ghcidExePath : opts -- TODO: Shell escape
499+ Just $ unwords $ fmap bashEscape $ ghcidExePath : opts
484500 where
485− opts =
486− [ "-W"
487− , "--outputfile=ghcid-output.txt"
488− ] <> map (\x -> "--reload='" <> x <> "'") reloadFiles
489− <> map (\x -> "--restart='" <> x <> "'") restartFiles
490− <> testCmd
491− <> ["--command='" <> unwords ("ghci" : ghciArgs) <> "'"] -- TODO: Shell escape
492− testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'") -- TODO: Shell escape
493−
501+ opts = concat
502+ [ ["-W"]
503+ , ["--outputfile=ghcid-output.txt"]
504+ , map (\x -> "--reload=" <> x) reloadFiles
505+ , map (\x -> "--restart=" <> x) restartFiles
506+ , maybe [] (\cmd -> ["--test=" <> cmd]) mcmd
507+ , ["--command=" <> unwords ("ghci" : ghciArgs)]
508+ ]
494509 adjustRoot x = if chdirToRoot then makeRelative root x else x
495510 reloadFiles = map adjustRoot [root </> "config"]
496511 restartFiles = map (adjustRoot . _cabalPackageInfo_packageFile) packages
497512
498−mkGhciScriptArg :: FilePath -> [String]
499−mkGhciScriptArg dotGhci = ["-ghci-script", dotGhci]
500−
501−getFreePort :: MonadIO m => m PortNumber
502−getFreePort = liftIO $ withSocketsDo $ do
503− addr:_ <- getAddrInfo (Just defaultHints) (Just "127.0.0.1") (Just "0")
504− bracket (open addr) close socketPort
513+getFreePort :: MonadIO m => m Socket.PortNumber
514+getFreePort = liftIO $ Socket.withSocketsDo $ do
515+ addr:_ <- Socket.getAddrInfo (Just Socket.defaultHints) (Just "127.0.0.1") (Just "0")
516+ bracket (open addr) Socket.close Socket.socketPort
505517 where
506518 open addr = do
507− sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
508− bind sock (addrAddress addr)
519+ sock <- Socket.socket (Socket.addrFamily addr) (Socket.addrSocketType addr) (Socket.addrProtocol addr)
520+ Socket.bind sock (Socket.addrAddress addr)
509521 return sock
510522
511523
lib/command/src/Obelisk/Command/Thunk.hs+3−3
@@ -56,7 +56,7 @@ import System.Exit
5656import System.FilePath
5757import System.IO.Error
5858import System.IO.Temp
59−import System.Posix (getSymbolicLinkStatus, modificationTime)
59+import System.PosixCompat.Files (getSymbolicLinkStatus, modificationTime)
6060import qualified Text.URI as URI
6161
6262import Obelisk.App (MonadObelisk)
@@ -178,14 +178,14 @@ getNixSha256ForUriUnpacked
178178getNixSha256ForUriUnpacked uri =
179179 withExitFailMessage ("nix-prefetch-url: Failed to determine sha256 hash of URL " <> gitUriToText uri) $ do
180180 [hash] <- fmap T.lines $ readProcessAndLogOutput (Debug, Debug) $
181− proc "nix-prefetch-url" ["--unpack", "--type", "sha256", T.unpack $ gitUriToText uri]
181+ proc nixPrefetchUrlPath ["--unpack", "--type", "sha256", T.unpack $ gitUriToText uri]
182182 pure hash
183183
184184nixPrefetchGit :: MonadObelisk m => GitUri -> Text -> Bool -> m NixSha256
185185nixPrefetchGit uri rev fetchSubmodules =
186186 withExitFailMessage ("nix-prefetch-git: Failed to determine sha256 hash of Git repo " <> gitUriToText uri <> " at " <> rev) $ do
187187 out <- readProcessAndLogStderr Debug $
188− proc "nix-prefetch-git" $ filter (/="")
188+ proc nixPrefetchGitPath $ filter (/="")
189189 [ "--url", T.unpack $ gitUriToText uri
190190 , "--rev", T.unpack rev
191191 , if fetchSubmodules then "--fetch-submodules" else ""
lib/command/src/Obelisk/Command/Utils.hs+6
@@ -54,6 +54,12 @@ nixBuildExePath = $(staticWhich "nix-build")
5454jreKeyToolPath :: FilePath
5555jreKeyToolPath = $(staticWhich "keytool")
5656
57+nixPrefetchGitPath :: FilePath
58+nixPrefetchGitPath = $(staticWhich "nix-prefetch-git")
59+
60+nixPrefetchUrlPath :: FilePath
61+nixPrefetchUrlPath = $(staticWhich "nix-prefetch-url")
62+
5763-- | Nix syntax requires relative paths to be prefixed by @./@ or
5864-- @../@. This will make a 'FilePath' that can be embedded in a Nix
5965-- expression.
lib/frontend/src/Obelisk/Frontend.hs+1−1
@@ -182,7 +182,7 @@ runFrontendWithConfigsAndCurrentRoute
182182 -> Frontend (R frontendRoute)
183183 -> JSM ()
184184runFrontendWithConfigsAndCurrentRoute mode configs validFullEncoder frontend = do
185− let ve = validFullEncoder . hoistParse errorLeft (prismEncoder (rPrism $ _FullRoute_Frontend . _ObeliskRoute_App))
185+ let ve = validFullEncoder . hoistParse errorLeft (reviewEncoder (rPrism $ _FullRoute_Frontend . _ObeliskRoute_App))
186186 errorLeft = \case
187187 Left _ -> error "runFrontend: Unexpected non-app ObeliskRoute reached the frontend. This shouldn't happen."
188188 Right x -> Identity x
lib/route/src/Obelisk/Route.hs+70−27
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE ApplicativeDo #-}
23{-# LANGUAGE ConstraintKinds #-}
34{-# LANGUAGE EmptyCase #-}
@@ -61,6 +62,7 @@ module Obelisk.Route
6162 , justEncoder
6263 , nothingEncoder
6364 , isoEncoder
65+ , viewEncoder
6466 , wrappedEncoder
6567 , unwrappedEncoder
6668 , listToNonEmptyEncoder
@@ -69,6 +71,7 @@ module Obelisk.Route
6971 , toListMapEncoder
7072 , shadowEncoder
7173 , prismEncoder
74+ , reviewEncoder
7275 , rPrism
7376 , _R
7477 , obeliskRouteEncoder
@@ -133,6 +136,7 @@ import Control.Lens
133136 , Prism'
134137 , prism'
135138 , re
139+ , review
136140 , view
137141 , Wrapped (..)
138142 )
@@ -221,7 +225,19 @@ hoistR f (x :=> Identity y) = f x :/ y
221225infixr 5 :.
222226type (:.) = (,)
223227
228+#ifdef __GLASGOW_HASKELL__
229+#if __GLASGOW_HASKELL__ >= 810
224230{-# COMPLETE (:.) #-}
231+#else
232+{-# WARNING (:.)
233+ [ "Use of this pattern in GHC < 8.10 will result in spurious non-exhaustive warnings at every use site."
234+ , "We cannot provide a COMPLETE pragma to silence these due to a GHC bug: https://gitlab.haskell.org/ghc/ghc/issues/17729."
235+ , "The bug hides incompleteness warnings for all two tuples when the COMPLETE pattern is in scope."
236+ , "Instead, you should use (,) directly until you can switch to GHC >= 8.10, where the COMPLETE pragma is reinstated."
237+ ]
238+ #-}
239+#endif
240+#endif
225241pattern (:.) :: a -> b -> a :. b
226242pattern a :. b = (a, b)
227243
@@ -340,7 +356,7 @@ instance Monad parse => Bifunctor (,) (EncoderImpl parse) (EncoderImpl parse) (E
340356 }
341357
342358instance (Monad parse, Applicative check) => Braided (Encoder check parse) (,) where
343− braid = isoEncoder (iso swap swap)
359+ braid = viewEncoder (iso swap swap)
344360
345361
346362instance (Applicative check, Monad parse) => PFunctor (,) (Encoder check parse) (Encoder check parse) where
@@ -376,11 +392,11 @@ instance (Monad parse, Applicative check) => Bifunctor Either (Encoder check par
376392 bimap f g = Encoder $ liftA2 bimap (unEncoder f) (unEncoder g)
377393
378394instance (Applicative check, Monad parse) => Associative (Encoder check parse) Either where
379− associate = isoEncoder (iso (associate @(->) @Either) disassociate)
380− disassociate = isoEncoder (iso disassociate associate)
395+ associate = viewEncoder (iso (associate @(->) @Either) disassociate)
396+ disassociate = viewEncoder (iso disassociate associate)
381397
382398instance (Monad parse, Applicative check) => Braided (Encoder check parse) Either where
383− braid = isoEncoder (iso swap swap)
399+ braid = viewEncoder (iso swap swap)
384400
385401
386402
@@ -434,17 +450,17 @@ instance (Applicative check, Monad parse) => Monoidal (Encoder check parse) (,)
434450--------------------------------------------------------------------------------
435451
436452-- | Given a valid 'Iso' from lens, construct an 'Encoder'
437−isoEncoder :: (Applicative check, Applicative parse) => Iso' a b -> Encoder check parse a b
438−isoEncoder f = unsafeMkEncoder $ EncoderImpl
453+viewEncoder :: (Applicative check, Applicative parse) => Iso' a b -> Encoder check parse a b
454+viewEncoder f = unsafeMkEncoder $ EncoderImpl
439455 { _encoderImpl_encode = view f
440456 , _encoderImpl_decode = pure . view (from f)
441457 }
442458
443459wrappedEncoder :: (Wrapped a, Applicative check, Applicative parse) => Encoder check parse (Unwrapped a) a
444−wrappedEncoder = isoEncoder $ from _Wrapped'
460+wrappedEncoder = viewEncoder $ from _Wrapped'
445461
446462unwrappedEncoder :: (Wrapped a, Applicative check, Applicative parse) => Encoder check parse a (Unwrapped a)
447−unwrappedEncoder = isoEncoder _Wrapped'
463+unwrappedEncoder = viewEncoder $ _Wrapped'
448464
449465maybeToEitherEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Maybe a) (Either () a)
450466maybeToEitherEncoder = unsafeMkEncoder $ EncoderImpl
@@ -469,11 +485,11 @@ maybeEncoder f g = shadowEncoder f g . maybeToEitherEncoder
469485
470486-- | Encode a value by simply applying 'Just'
471487justEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a (Maybe a)
472−justEncoder = prismEncoder _Just
488+justEncoder = reviewEncoder _Just
473489
474490-- | Encode () to 'Nothing'.
475491nothingEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse () (Maybe a)
476−nothingEncoder = prismEncoder _Nothing
492+nothingEncoder = reviewEncoder _Nothing
477493
478494someConstEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Some (Const a)) a
479495someConstEncoder = unsafeMkEncoder $ EncoderImpl
@@ -827,12 +843,19 @@ _R variant = dSumGEqPrism variant . iso runIdentity Identity
827843
828844-- | An encoder that only works on the items available via the prism. An error will be thrown in the parse monad
829845-- if the prism doesn't match.
830−prismEncoder :: (Applicative check, MonadError Text parse) => Prism' b a -> Encoder check parse a b
831−prismEncoder p = unsafeMkEncoder $ EncoderImpl
832− { _encoderImpl_encode = (^. re p)
846+--
847+-- Note that a 'Prism' from @a@ to @b@ will produce an 'Encoder' from @b@ to @a@
848+-- (i.e. 'reviewEncoder' is a contravariant functor from the category of prisms to the category of encoders),
849+-- just like 'review' produces a function @b -> a@. This is because 'Prism's extract values, in a way that might
850+-- fail, in their forward direction and inject values, in a way that cannot fail, in their reverse direction;
851+-- whereas 'Encoder's encode, which cannot fail, in their forward direction, and decode, which can fail, in their
852+-- reverse direction. In short @reviewEncoder (f . g) = reviewEncoder g . reviewEncoder f@.
853+reviewEncoder :: (Applicative check, MonadError Text parse) => Prism' b a -> Encoder check parse a b
854+reviewEncoder p = unsafeMkEncoder $ EncoderImpl
855+ { _encoderImpl_encode = review p
833856 , _encoderImpl_decode = \r -> case r ^? p of
834857 Just a -> pure a
835− Nothing -> throwError "prismEncoder: value is not present in the prism"
858+ Nothing -> throwError "reviewEncoder: value is not present in the prism"
836859 }
837860
838861-- | A URL path and query string, in which trailing slashes don't matter in the path
@@ -1028,17 +1051,6 @@ void1Encoder = Encoder $ pure $ EncoderImpl
10281051instance GShow Void1 where
10291052 gshowsPrec _ = \case {}
10301053
1031−concat <$> mapM deriveRouteComponent
1032− [ ''ResourceRoute
1033− , ''JSaddleWarpRoute
1034− , ''IndexOnlyRoute
1035− ]
1036−
1037−makePrisms ''ObeliskRoute
1038−makePrisms ''FullRoute
1039−deriveGEq ''Void1
1040−deriveGCompare ''Void1
1041−
10421054-- | Given a backend route and a checked route encoder, render the route (path
10431055-- and query string). See 'checkEncoder' for how to produce a checked encoder.
10441056renderBackendRoute
@@ -1071,11 +1083,11 @@ readShowEncoder :: (MonadError Text parse, Read a, Show a, Applicative check) =>
10711083readShowEncoder = singlePathSegmentEncoder . unsafeTshowEncoder
10721084
10731085integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
1074−integralEncoder = prismEncoder (Numeric.Lens.integral)
1086+integralEncoder = reviewEncoder (Numeric.Lens.integral)
10751087
10761088pathSegmentEncoder :: (MonadError Text parse, Applicative check, Cons as as a a) =>
10771089 Encoder check parse (a, (as, b)) (as, b)
1078−pathSegmentEncoder = first (prismEncoder _Cons) . disassociate
1090+pathSegmentEncoder = first (reviewEncoder _Cons) . disassociate
10791091
10801092newtype Decoder check parse b a = Decoder { toEncoder :: Encoder check parse a b }
10811093
@@ -1182,3 +1194,34 @@ byteStringsToPageName p q =
11821194 in decode pageNameEncoder' (T.unpack (T.decodeUtf8 p), T.unpack (T.decodeUtf8 q))
11831195
11841196--TODO: decodeURIComponent as appropriate
1197+
1198+
1199+{-# DEPRECATED isoEncoder "Instead of 'isoEncoder f', use 'viewEncoder f'" #-}
1200+-- | Given a valid 'Iso' from lens, construct an 'Encoder'
1201+isoEncoder :: (Applicative check, Applicative parse) => Iso' a b -> Encoder check parse a b
1202+isoEncoder = viewEncoder
1203+
1204+{-# DEPRECATED prismEncoder "Instead of 'prismEncoder f', use 'reviewEncoder f'" #-}
1205+-- | An encoder that only works on the items available via the prism. An error will be thrown in the parse monad
1206+-- if the prism doesn't match.
1207+--
1208+-- Note that a 'Prism' from @a@ to @b@ will produce an 'Encoder' from @b@ to @a@
1209+-- (i.e. 'prismEncoder' is a contravariant functor from the category of prisms to the category of encoders),
1210+-- just like 'review' produces a function @b -> a@. This is because 'Prism's extract values, in a way that might
1211+-- fail, in their forward direction and inject values, in a way that cannot fail, in their reverse direction;
1212+-- whereas 'Encoder's encode, which cannot fail, in their forward direction, and decode, which can fail, in their
1213+-- reverse direction. In short @prismEncoder (f . g) = prismEncoder g . prismEncoder f@.
1214+prismEncoder :: (Applicative check, MonadError Text parse) => Prism' b a -> Encoder check parse a b
1215+prismEncoder = reviewEncoder
1216+
1217+
1218+concat <$> mapM deriveRouteComponent
1219+ [ ''ResourceRoute
1220+ , ''JSaddleWarpRoute
1221+ , ''IndexOnlyRoute
1222+ ]
1223+
1224+makePrisms ''ObeliskRoute
1225+makePrisms ''FullRoute
1226+deriveGEq ''Void1
1227+deriveGCompare ''Void1
lib/selftest/src/Obelisk/SelfTest.hs+7
@@ -243,6 +243,8 @@ main' isVerbose httpManager obeliskRepoReadOnly = withInitCache $ \initCache ->
243243 uu <- update
244244 assertRevEQ u uu
245245
246+ it "can run 'ob doc'" $ inTmpObInit $ \_ -> runOb_ ["doc", "reflex"]
247+
246248 describe "ob thunk pack/unpack" $ parallel $ do
247249 it "has thunk pack and unpack inverses" $ inTmpObInitWithImplCopy $ \_ -> do
248250
@@ -304,6 +306,11 @@ main' isVerbose httpManager obeliskRepoReadOnly = withInitCache $ \initCache ->
304306 void $ errExit False $ runOb ["thunk", "update", toTextIgnore dir, "--branch", "dumble-palooza"]
305307 checkDir dir >>= liftIO . assertEqual "" startingContents
306308
309+ describe "ob shell" $ parallel $ do
310+ it "works with --" $ inTmpObInit $ \_ -> do
311+ output <- runOb ["shell", "--", "ghc-pkg", "list"]
312+ liftIO $ assertBool "Unexpected output from ob shell" $ ("Cabal-" `T.isInfixOf` output) && ("ghc-" `T.isInfixOf` output)
313+
307314 describe "ob hoogle" $ {- NOT parallel -} do
308315 it "starts a hoogle server on the given port" $ inTmpObInit $ \_ -> do
309316 [p0] <- liftIO $ getFreePorts 1
lib/snap-extras/obelisk-snap-extras.cabal−1
@@ -12,7 +12,6 @@ library
1212 , base
1313 , bytestring
1414 , snap-core
15− , unix
1615 , directory
1716 , filepath
1817 , text
skeleton/frontend/frontend.cabal+4−2
@@ -28,7 +28,9 @@ executable frontend
2828 , reflex-dom
2929 , obelisk-generated-static
3030 , frontend
31− --TODO: Make these ghc-options optional
3231 ghc-options: -threaded
32+ if impl(ghcjs)
33+ ghc-options: -dedupe
34+ cpp-options: -DGHCJS_BROWSER
3335 if os(darwin)
34− ghc-options: -dynamic
36+ ghc-options: -dynamic