on master help sign in
Merge branch 'master' into overridable-static
commit6fe903adceb51cf1f6516fa4f4c636a3b27e8bfc authorLuigy Leon <luigy.leon@obsidian.systems> dateTue 16 Oct 2018, 22:23 (8 years ago) parent2c8798d835cc6c
11 files changed+216−64
default.nix+7−7 lib/cliapp/src/Obelisk/CliApp.hs+3−1 lib/cliapp/src/Obelisk/CliApp/Logging.hs+28−18 lib/cliapp/src/Obelisk/CliApp/Types.hs+79−15 lib/command/src/Obelisk/App.hs+25−10 lib/command/src/Obelisk/Command.hs+11−6 lib/command/src/Obelisk/Command/Project.hs+6−2 lib/frontend/src/Obelisk/Frontend.hs−1 lib/route/src/Obelisk/Route.hs+25−2 lib/route/src/Obelisk/Route/Frontend.hs+25−1 lib/selftest/src/Obelisk/SelfTest.hs+7−1
default.nix+7−7
@@ -327,14 +327,14 @@ rec {
327327 };
328328 in mkProject (projectDefinition args));
329329 serverOn = sys: config: serverExe (projectOut sys).ghc.backend (projectOut system).ghcjs.frontend static config;
330− # `exe` is project's backend executable, with frontend assets, config, etc.
331− # `linuxExe` is the same but built for x86_64-linux.
332− exe = serverOn system configPath;
333− linuxExe = serverOn "x86_64-linux" configPath;
330+ linuxExe = serverOn "x86_64-linux";
334331 in projectOut system // {
335− inherit exe linuxExe;
336− server = args@{ hostName, adminEmail, routeHost, enableHttps, config }:
337− server (args // { exe = serverOn "x86_64-linux" config;});
332+ linuxExeConfigurable = linuxExe;
333+ linuxExe = linuxExe (base + "/config");
334+ exe = serverOn system (base + "/config") ;
335+ server = args@{ hostName, adminEmail, routeHost, enableHttps, config }: let
336+ injectableConfig = builtins.filterSource (path: _: !(hasPrefix (toString config + "/backend") (toString path))) config;
337+ in server (args // { exe = linuxExe injectableConfig; });
338338 obelisk = import (base + "/.obelisk/impl") {};
339339 };
340340 haskellPackageSets = {
lib/cliapp/src/Obelisk/CliApp.hs+3−1
@@ -6,7 +6,9 @@
66module Obelisk.CliApp
77 (
88 -- .Types
9− Cli
9+ CliLog
10+ , CliThrow
11+ , Cli
1012 , CliT(..)
1113 , runCli
1214 , CliConfig
lib/cliapp/src/Obelisk/CliApp/Logging.hs+28−18
@@ -7,12 +7,29 @@
77{-# LANGUAGE ScopedTypeVariables #-}
88-- | Provides a logging handler that facilitates safe ouputting to terminal using MVar based locking.
99-- | Spinner.hs and Process.hs work on this guarantee.
10−module Obelisk.CliApp.Logging where
10+module Obelisk.CliApp.Logging
11+ ( newCliConfig
12+ , runCli
13+ , verboseLogLevel
14+ , isOverwrite
15+ , getSeverity
16+ , getLogLevel
17+ , setLogLevel
18+ , putLog
19+ , putLogRaw
20+ , failWith
21+ , withExitFailMessage
22+ , writeLog
23+ , allowUserToMakeLoggingVerbose
24+ , getChars
25+ , fork
26+ ) where
1127
1228import Control.Concurrent (ThreadId, forkIO, killThread, threadDelay)
1329import Control.Concurrent.MVar (modifyMVar_, newMVar)
1430import Control.Monad (unless, void, when)
1531import Control.Monad.Catch (MonadCatch, MonadMask, bracket, catch, throwM)
32+import Control.Monad.Except (MonadError, throwError)
1633import Control.Monad.IO.Class (liftIO)
1734import Control.Monad.Log (Severity (..), WithSeverity (..), logMessage, runLoggingT)
1835import Control.Monad.Loops (iterateUntil)
@@ -25,7 +42,7 @@ import qualified Data.Text.IO as T
2542import System.Console.ANSI (Color (Red, Yellow), ColorIntensity (Vivid),
2643 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
2744 SGR (SetColor, SetConsoleIntensity), clearLine)
28−import System.Exit (ExitCode (..), exitWith)
45+import System.Exit (ExitCode (..))
2946import System.IO (BufferMode (NoBuffering), hFlush, hReady, hSetBuffering, stderr, stdin, stdout)
3047
3148import qualified Obelisk.CliApp.TerminalString as TS
@@ -42,6 +59,7 @@ newCliConfig sev noColor noSpinner = do
4259runCli :: MonadIO m => CliConfig -> CliT m a -> m a
4360runCli c =
4461 flip runLoggingT (handleLog c)
62+ . unDieT
4563 . flip runReaderT c
4664 . unCliT
4765
@@ -59,17 +77,17 @@ getSeverity = \case
5977 Output_LogRaw (WithSeverity sev _) -> Just sev
6078 _ -> Nothing
6179
62−setLogLevel :: (MonadIO m, HasCliConfig m) => Severity -> m ()
63−setLogLevel sev = do
64− l <- _cliConfig_logLevel <$> getCliConfig
65− liftIO $ writeIORef l sev
66−
6780getLogLevel :: (MonadIO m, HasCliConfig m) => m Severity
6881getLogLevel = getLogLevel' =<< getCliConfig
6982
7083getLogLevel' :: MonadIO m => CliConfig -> m Severity
7184getLogLevel' = liftIO . readIORef . _cliConfig_logLevel
7285
86+setLogLevel :: (MonadIO m, HasCliConfig m) => Severity -> m ()
87+setLogLevel sev = do
88+ l <- _cliConfig_logLevel <$> getCliConfig
89+ liftIO $ writeIORef l sev
90+
7391handleLog :: MonadIO m => CliConfig -> Output -> m ()
7492handleLog conf output = do
7593 level <- getLogLevel' conf
@@ -107,21 +125,13 @@ handleLog' noColor output = do
107125 hFlush stdout
108126 return $ isOverwrite output
109127
110−-- | Log a message to the console.
111−--
112−-- Logs safely even if there are ongoing spinners.
113−putLog :: Cli m => Severity -> Text -> m ()
114−putLog sev = logMessage . Output_Log . WithSeverity sev
115−
116128-- | Like `putLog` but without the implicit newline added.
117129putLogRaw :: Cli m => Severity -> Text -> m ()
118130putLogRaw sev = logMessage . Output_LogRaw . WithSeverity sev
119131
120132-- | Like `putLog Alert` but also abrupts the program.
121−failWith :: (MonadIO m, Cli m) => Text -> m a
122−failWith s = do
123− putLog Alert s
124− liftIO $ exitWith $ ExitFailure 2
133+failWith :: MonadError Text m => Text -> m a
134+failWith = throwError
125135
126136-- | Intercept ExitFailure exceptions and log the given alert before exiting.
127137--
@@ -134,7 +144,7 @@ withExitFailMessage msg f = f `catch` \(e :: ExitCode) -> do
134144 throwM e
135145
136146-- | Write log to stdout, with colors (unless `noColor`)
137−writeLog:: (MonadIO m, MonadMask m) => Bool -> Bool -> WithSeverity Text -> m ()
147+writeLog :: (MonadIO m, MonadMask m) => Bool -> Bool -> WithSeverity Text -> m ()
138148writeLog withNewLine noColor (WithSeverity severity s)
139149 | noColor && severity <= Warning = liftIO $ putFn $ T.pack (show severity) <> ": " <> s
140150 | not noColor && severity <= Error = TS.putStrWithSGR errorColors h withNewLine s
lib/cliapp/src/Obelisk/CliApp/Types.hs+79−15
@@ -7,21 +7,20 @@ module Obelisk.CliApp.Types where
77
88import Control.Concurrent.MVar (MVar)
99import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
10−import Control.Monad.Log (LoggingT, MonadLog, Severity (..), WithSeverity (..))
11−import Control.Monad.Reader (MonadIO, ReaderT (..), ask)
10+import Control.Monad.Log (LoggingT, MonadLog, Severity (..), WithSeverity (..), logMessage)
11+import Control.Monad.Reader (MonadIO, ReaderT (..), MonadReader, ask)
12+import Control.Monad.Writer (WriterT)
13+import Control.Monad.State (StateT)
14+import Control.Monad.Except (ExceptT, MonadError (..))
15+import Control.Monad.IO.Class (liftIO)
16+import Control.Monad.Trans (MonadTrans, lift)
1217import Data.IORef (IORef)
1318import Data.Text (Text)
19+import System.Exit (ExitCode (..), exitWith)
1420
1521import Obelisk.CliApp.TerminalString (TerminalString)
1622
17−data CliConfig = CliConfig
18− { _cliConfig_logLevel :: IORef Severity -- We are capable of changing the log level at runtime
19− , _cliConfig_noColor :: Bool -- Disallow coloured output
20− , _cliConfig_noSpinner :: Bool -- Disallow spinners
21− , _cliConfig_lock :: MVar Bool -- Whether the last message was an Overwrite output
22− , _cliConfig_tipDisplayed :: IORef Bool -- Whether the user tip (to make verbose) was already displayed
23− , _cliConfig_spinnerStack :: IORef ([Bool], [TerminalString]) -- Stack of logs from nested spinners
24− }
23+--------------------------------------------------------------------------------
2524
2625data Output
2726 = Output_Log (WithSeverity Text) -- Regular logging message (with colors and newlines)
@@ -31,19 +30,84 @@ data Output
3130 | Output_ClearLine -- Clear the line
3231 deriving (Eq, Show, Ord)
3332
34−type Cli m = MonadLog Output m
33+type CliLog m = MonadLog Output m
3534
36−newtype CliT m a = CliT
37− { unCliT :: ReaderT CliConfig (LoggingT Output m) a
38− }
35+type CliThrow m = MonadError Text m
36+
37+-- TODO could remove for granularity
38+type Cli m = (CliLog m, CliThrow m)
39+
40+-- | Log a message to the console.
41+--
42+-- Logs safely even if there are ongoing spinners.
43+putLog :: Cli m => Severity -> Text -> m ()
44+putLog sev = logMessage . Output_Log . WithSeverity sev
45+
46+newtype DieT m a = DieT { unDieT :: LoggingT Output m a }
3947 deriving
4048 ( Functor, Applicative, Monad, MonadIO
49+ , MonadReader r
4150 , MonadThrow, MonadCatch, MonadMask
4251 , MonadLog Output
4352 )
4453
54+instance MonadTrans DieT where
55+ lift = DieT . lift
56+
57+-- TODO generalize to bigger error types
58+instance MonadIO m => MonadError Text (DieT m) where
59+ throwError s = do
60+ putLog Alert s
61+ liftIO $ exitWith $ ExitFailure 2
62+
63+ -- Cannot catch
64+ catchError m _ = m
65+
66+--------------------------------------------------------------------------------
67+
68+data CliConfig = CliConfig
69+ { -- | We are capable of changing the log level at runtime
70+ _cliConfig_logLevel :: IORef Severity
71+ , -- | Disallow coloured output
72+ _cliConfig_noColor :: Bool
73+ , -- | Disallow spinners
74+ _cliConfig_noSpinner :: Bool
75+ , -- | Whether the last message was an Overwrite output
76+ _cliConfig_lock :: MVar Bool
77+ , -- | Whether the user tip (to make verbose) was already displayed
78+ _cliConfig_tipDisplayed :: IORef Bool
79+ , -- | Stack of logs from nested spinners
80+ _cliConfig_spinnerStack :: IORef ([Bool], [TerminalString])
81+ }
82+
4583class Monad m => HasCliConfig m where
4684 getCliConfig :: m CliConfig
4785
86+instance HasCliConfig m => HasCliConfig (ReaderT r m) where
87+ getCliConfig = lift getCliConfig
88+
89+instance (Monoid w, HasCliConfig m) => HasCliConfig (WriterT w m) where
90+ getCliConfig = lift getCliConfig
91+
92+instance HasCliConfig m => HasCliConfig (StateT s m) where
93+ getCliConfig = lift getCliConfig
94+
95+instance HasCliConfig m => HasCliConfig (ExceptT e m) where
96+ getCliConfig = lift getCliConfig
97+
98+--------------------------------------------------------------------------------
99+
100+newtype CliT m a = CliT
101+ { unCliT :: ReaderT CliConfig (DieT m) a
102+ }
103+ deriving
104+ ( Functor, Applicative, Monad, MonadIO
105+ , MonadThrow, MonadCatch, MonadMask
106+ , MonadLog Output, MonadError Text, MonadReader CliConfig
107+ )
108+
109+instance MonadTrans CliT where
110+ lift = CliT . lift . lift
111+
48112instance Monad m => HasCliConfig (CliT m) where
49− getCliConfig = CliT ask
113+ getCliConfig = ask
lib/command/src/Obelisk/App.hs+25−10
@@ -9,11 +9,15 @@ module Obelisk.App where
99
1010import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)
1111import Control.Monad.Reader (MonadIO, ReaderT (..), ask, runReaderT)
12−import Control.Monad.Trans.Class (lift)
12+import Control.Monad.Writer (WriterT)
13+import Control.Monad.State (StateT)
14+import Control.Monad.Except (ExceptT, MonadError)
15+import Control.Monad.Trans.Class (MonadTrans, lift)
16+import Data.Text (Text)
1317import System.Directory (XdgDirectory (XdgData), getXdgDirectory)
1418import Control.Monad.Log (MonadLog)
1519
16−import Obelisk.CliApp (CliConfig, CliT, HasCliConfig, getCliConfig, runCli, Output)
20+import Obelisk.CliApp (CliConfig, CliLog, CliThrow, CliT (..), HasCliConfig, runCli, Output)
1721
1822newtype Obelisk = Obelisk
1923 { _obelisk_cliConfig :: CliConfig
@@ -24,18 +28,28 @@ newtype ObeliskT m a = ObeliskT
2428 }
2529 deriving
2630 ( Functor, Applicative, Monad, MonadIO, MonadThrow, MonadCatch, MonadMask
27− , HasObelisk, HasCliConfig)
31+ , MonadLog Output, MonadError Text, HasCliConfig)
2832
29−deriving instance Monad m => MonadLog Output (ObeliskT m)
33+instance MonadTrans ObeliskT where
34+ lift = ObeliskT . lift . lift
3035
31−class HasObelisk m where
36+class Monad m => HasObelisk m where
3237 getObelisk :: m Obelisk
3338
34−instance Monad m => HasObelisk (ReaderT Obelisk m) where
35− getObelisk = ask
39+instance Monad m => HasObelisk (ObeliskT m) where
40+ getObelisk = ObeliskT ask
3641
37−instance HasCliConfig m => HasCliConfig (ReaderT Obelisk m) where
38− getCliConfig = lift getCliConfig
42+instance HasObelisk m => HasObelisk (ReaderT r m) where
43+ getObelisk = lift getObelisk
44+
45+instance (Monoid w, HasObelisk m) => HasObelisk (WriterT w m) where
46+ getObelisk = lift getObelisk
47+
48+instance HasObelisk m => HasObelisk (StateT r m) where
49+ getObelisk = lift getObelisk
50+
51+instance HasObelisk m => HasObelisk (ExceptT e m) where
52+ getObelisk = lift getObelisk
3953
4054runObelisk :: MonadIO m => Obelisk -> ObeliskT m a -> m a
4155runObelisk c =
@@ -44,7 +58,8 @@ runObelisk c =
4458 . unObeliskT
4559
4660type MonadObelisk m =
47− ( MonadLog Output m
61+ ( CliLog m
62+ , CliThrow m
4863 , HasCliConfig m
4964 , HasObelisk m
5065 , MonadIO m
lib/command/src/Obelisk/Command.hs+11−6
@@ -86,8 +86,11 @@ initSource = foldl1 (<|>)
8686 , InitSource_Symlink <$> strOption (long "symlink" <> action "directory" <> metavar "PATH")
8787 ]
8888
89+initForce :: Parser Bool
90+initForce = switch (long "force" <> help "Allow ob init to overwrite files")
91+
8992data ObCommand
90− = ObCommand_Init InitSource
93+ = ObCommand_Init InitSource Bool
9194 | ObCommand_Deploy DeployCommand
9295 | ObCommand_Run
9396 | ObCommand_Thunk ThunkCommand
@@ -127,7 +130,7 @@ inNixShell' p = withProjectRoot "." $ \root -> do
127130obCommand :: ArgsConfig -> Parser ObCommand
128131obCommand cfg = hsubparser
129132 (mconcat
130− [ command "init" $ info (ObCommand_Init <$> initSource) $ progDesc "Initialize an Obelisk project"
133+ [ command "init" $ info (ObCommand_Init <$> initSource <*> initForce) $ progDesc "Initialize an Obelisk project"
131134 , command "deploy" $ info (ObCommand_Deploy <$> deployCommand cfg) $ progDesc "Prepare a deployment for an Obelisk project"
132135 , command "run" $ info (pure ObCommand_Run) $ progDesc "Run current project in development mode"
133136 , command "thunk" $ info (ObCommand_Thunk <$> thunkCommand) $ progDesc "Manipulate thunk directories"
@@ -369,7 +372,7 @@ parseHandoff as' = case hasNoHandoff as' of
369372
370373ob :: MonadObelisk m => ObCommand -> m ()
371374ob = \case
372− ObCommand_Init source -> initProject source
375+ ObCommand_Init source force -> initProject source force
373376 ObCommand_Deploy dc -> case dc of
374377 DeployCommand_Init deployOpts -> withProjectRoot "." $ \root -> do
375378 let deployDir = _deployInitOpts_outputDir deployOpts
@@ -395,9 +398,11 @@ ob = \case
395398 enableHttps = _deployInitOpts_enableHttps deployOpts
396399 deployInit thunkPtr (root </> "config") deployDir
397400 sshKeyPath hostname route adminEmail enableHttps
398− DeployCommand_Push remoteBuilder -> deployPush "." $ case remoteBuilder of
399− Nothing -> pure []
400− Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
401+ DeployCommand_Push remoteBuilder -> do
402+ deployPath <- liftIO $ canonicalizePath "."
403+ deployPush deployPath $ case remoteBuilder of
404+ Nothing -> pure []
405+ Just RemoteBuilder_ObeliskVM -> (:[]) <$> VmBuilder.getNixBuildersArg
401406 DeployCommand_Update -> deployUpdate "."
402407 DeployCommand_Test Android -> deployMobile "android" []
403408 DeployCommand_Test (IOS teamID) -> deployMobile "ios" [teamID]
lib/command/src/Obelisk/Command/Project.hs+6−2
@@ -66,10 +66,14 @@ toImplDir :: FilePath -> FilePath
6666toImplDir p = toObeliskDir p </> "impl"
6767
6868-- | Create a new project rooted in the current directory
69−initProject :: MonadObelisk m => InitSource -> m ()
70−initProject source = withSystemTempDirectory "ob-init" $ \tmpDir -> do
69+initProject :: MonadObelisk m => InitSource -> Bool -> m ()
70+initProject source force = withSystemTempDirectory "ob-init" $ \tmpDir -> do
7171 let implDir = toImplDir tmpDir
7272 obDir = toObeliskDir tmpDir
73+ liftIO (listDirectory ".") >>= \case
74+ [] -> pure ()
75+ _ | force -> putLog Warning "Initializing in non-empty directory"
76+ | otherwise -> failWith "ob init requires an empty directory. Use the flag --force to init anyway, potentially overwriting files."
7377 skeleton <- withSpinner "Setting up obelisk" $ do
7478 liftIO $ createDirectory obDir
7579 case source of
lib/frontend/src/Obelisk/Frontend.hs−1
@@ -31,7 +31,6 @@ import Data.ByteString (ByteString)
3131import Data.Dependent.Sum (DSum (..))
3232import Data.Functor.Sum
3333import Data.IORef
34−import Data.Monoid hiding (Sum)
3534import GHCJS.DOM hiding (bracket, catch)
3635import GHCJS.DOM.Document
3736import GHCJS.DOM.Node
lib/route/src/Obelisk/Route.hs+25−2
@@ -46,6 +46,8 @@ module Obelisk.Route
4646 , unsafeTshowEncoder
4747 , someConstEncoder
4848 , singlePathSegmentEncoder
49+ , maybeEncoder
50+ , maybeToEitherEncoder
4951 , justEncoder
5052 , nothingEncoder
5153 , isoEncoder
@@ -304,7 +306,28 @@ wrappedEncoder = isoEncoder $ from _Wrapped'
304306unwrappedEncoder :: (Wrapped a, Applicative check, Applicative parse) => Encoder check parse a (Unwrapped a)
305307unwrappedEncoder = isoEncoder _Wrapped'
306308
307−-- | Encodea a value by simply applying 'Just'
309+maybeToEitherEncoder :: (Applicative check, Applicative parse) => Encoder check parse (Maybe a) (Either () a)
310+maybeToEitherEncoder = unsafeMkEncoder $ EncoderImpl
311+ { _encoderImpl_encode = \case
312+ Nothing -> Left ()
313+ Just a -> Right a
314+ , _encoderImpl_decode = pure . \case
315+ Left _ -> Nothing
316+ Right a -> Just a
317+ }
318+
319+maybeEncoder
320+ :: ( MonadError Text check
321+ , Show a
322+ , Show b
323+ , check ~ parse
324+ )
325+ => Encoder check parse () b
326+ -> Encoder check parse a b
327+ -> Encoder check parse (Maybe a) b
328+maybeEncoder f g = shadowEncoder f g . maybeToEitherEncoder
329+
330+-- | Encode a value by simply applying 'Just'
308331justEncoder :: (Applicative check, MonadError Text parse) => Encoder check parse a (Maybe a)
309332justEncoder = prismEncoder _Just
310333
@@ -714,7 +737,7 @@ obeliskRouteEncoder appRouteSegment = pathComponentEncoder $ \r ->
714737obeliskRouteSegment :: forall check parse appRoute a.
715738 (MonadError Text check, MonadError Text parse)
716739 => ObeliskRoute appRoute a
717− -> (forall a. appRoute a -> SegmentResult check parse a)
740+ -> (forall b. appRoute b -> SegmentResult check parse b)
718741 -> SegmentResult check parse a
719742obeliskRouteSegment r appRouteSegment = case r of
720743 ObeliskRoute_App appRoute -> appRouteSegment appRoute
lib/route/src/Obelisk/Route/Frontend.hs+25−1
@@ -33,6 +33,9 @@ module Obelisk.Route.Frontend
3333 , mapRoutedT
3434 , subRoute
3535 , subRoute_
36+ , maybeRoute
37+ , maybeRoute_
38+ , maybeRouted
3639 , runRouteViewT
3740 , SetRouteT(..)
3841 , SetRoute(..)
@@ -85,7 +88,7 @@ infixr 5 :~
8588pattern (:~) :: Reflex t => f a -> Dynamic t a -> DSum f (Compose (Dynamic t) Identity)
8689pattern a :~ b <- a :=> (coerceDynamic . getCompose -> b)
8790
88−class Routed t r m where
91+class Routed t r m | m -> t r where
8992 askRoute :: m (Dynamic t r)
9093
9194instance Monad m => Routed t r (RoutedT t r m) where
@@ -164,6 +167,22 @@ subRoute :: (MonadFix m, MonadHold t m, GEq r, Adjustable t m) => (forall a. r a
164167subRoute f = factorRouted $ strictDynWidget $ \(c :=> r') -> do
165168 runRoutedT (f c) r'
166169
170+maybeRoute_ :: (MonadFix m, MonadHold t m, Adjustable t m) => m () -> RoutedT t r m () -> RoutedT t (Maybe r) m ()
171+maybeRoute_ n j = maybeRouted $ strictDynWidget_ $ \case
172+ Nothing -> n
173+ Just r -> runRoutedT j r
174+
175+maybeRoute :: (MonadFix m, MonadHold t m, Adjustable t m) => m a -> RoutedT t r m a -> RoutedT t (Maybe r) m (Dynamic t a)
176+maybeRoute n j = maybeRouted $ strictDynWidget $ \case
177+ Nothing -> n
178+ Just r -> runRoutedT j r
179+
180+{-
181+maybeRoute :: (MonadFix m, MonadHold t m, GEq r, Adjustable t m) => m a -> RoutedT t r m a -> RoutedT t (Maybe r) m a
182+maybeRoute f = factorRouted $ strictDynWidget $ \(c :=> r') -> do
183+ runRoutedT (f c) r'
184+-}
185+
167186dsumValueCoercion :: Coercion f g -> Coercion (DSum k f) (DSum k g)
168187dsumValueCoercion Coercion = Coercion
169188
@@ -175,6 +194,11 @@ factorRouted r = RoutedT $ ReaderT $ \d -> do
175194 d' <- factorDyn d
176195 runRoutedT r $ (coerceWith (dynamicCoercion $ dsumValueCoercion dynamicIdentityCoercion) d')
177196
197+maybeRouted :: (Reflex t, MonadFix m, MonadHold t m) => RoutedT t (Maybe (Dynamic t a)) m b -> RoutedT t (Maybe a) m b
198+maybeRouted r = RoutedT $ ReaderT $ \d -> do
199+ d' <- maybeDyn d
200+ runRoutedT r d'
201+
178202-- | WARNING: The input 'Dynamic' must be fully constructed when this is run
179203strictDynWidget :: (MonadSample t m, MonadHold t m, Adjustable t m) => (a -> m b) -> RoutedT t a m (Dynamic t b)
180204strictDynWidget f = RoutedT $ ReaderT $ \r -> do
lib/selftest/src/Obelisk/SelfTest.hs+7−1
@@ -26,7 +26,7 @@ import System.Exit (ExitCode (..))
2626import System.Info
2727import System.IO (Handle, hClose)
2828import System.IO.Temp
29−import System.Process (readProcessWithExitCode)
29+import System.Process (readProcessWithExitCode, CreateProcess(cwd), readCreateProcessWithExitCode, proc)
3030import System.Timeout
3131import Test.Hspec
3232import Test.HUnit.Base
@@ -119,6 +119,12 @@ main = do
119119 it "works with default impl" $ inTmp $ \_ -> run "ob" ["init"]
120120 it "works with master branch impl" $ inTmp $ \_ -> run "ob" ["init", "--branch", "master"]
121121 it "works with symlink" $ inTmp $ \_ -> run "ob" ["init", "--symlink", obeliskImpl]
122+ it "doesn't silently overwrite existing files" $ withSystemTempDirectory "ob-init" $ \dir -> do
123+ let p force = (proc "ob" $ "--no-handoff" : "init" : if force then ["--force"] else []) { cwd = Just dir }
124+ (ExitSuccess, _, _) <- readCreateProcessWithExitCode (p False) ""
125+ (ExitFailure _, _, _) <- readCreateProcessWithExitCode (p False) ""
126+ (ExitSuccess, _, _) <- readCreateProcessWithExitCode (p True) ""
127+ pure ()
122128
123129 it "doesn't create anything when given an invalid impl" $ inTmp $ \tmp -> do
124130 void $ errExit False $ run "ob" ["init", "--symlink", "/dev/null"]