1{-# LANGUAGE FlexibleContexts #-}
2{-# LANGUAGE LambdaCase #-}
3{-# LANGUAGE MultiParamTypeClasses #-}
4{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE ScopedTypeVariables #-}
6module Obelisk.Command.Run where
8import Control.Exception (bracket)
10import Control.Monad.IO.Class (liftIO)
11import Control.Monad.Reader (MonadIO)
14import Data.List.NonEmpty as NE
16import qualified Data.Text as T
17import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
18import Distribution.Parsec.ParseResult (runParseResult)
19import Distribution.Pretty
20import Distribution.Types.BuildInfo
21import Distribution.Types.CondTree
22import Distribution.Types.GenericPackageDescription
23import Distribution.Types.Library
24import Distribution.Utils.Generic
28import Language.Haskell.Extension
30import System.Directory
32import System.IO.Temp (withSystemTempDirectory)
33import Data.ByteString (ByteString)
35import Obelisk.App (MonadObelisk, ObeliskT)
36import Obelisk.CliApp (CliT (..), HasCliConfig, Severity (..), callCommand, failWith, getCliConfig, putLog,
38import Obelisk.Command.Project (inProjectShell)
40data CabalPackageInfo = CabalPackageInfo
41 { _cabalPackageInfo_packageRoot :: FilePath
42 , _cabalPackageInfo_sourceDirs :: NE.NonEmpty FilePath
43 -- ^ List of hs src dirs of the library component
44 , _cabalPackageInfo_defaultExtensions :: [Extension]
45 -- ^ List of globally enable extensions of the library component
48-- NOTE: `run` is not polymorphic like the rest because we use StaticPtr to invoke it.
52 withGhciScript pkgs $ \dotGhciPath -> do
53 freePort <- getFreePort
54 runGhcid dotGhciPath $ Just $ unwords ["run", show freePort, "(runServeAsset Obelisk.Generated.Static.staticRootPath)", "Backend.backend", "Frontend.frontend"]
56runRepl :: MonadObelisk m => m ()
59 withGhciScript pkgs $ \dotGhciPath -> do
60 runGhciRepl dotGhciPath
62runWatch :: MonadObelisk m => m ()
65 withGhciScript pkgs $ \dotGhciPath -> runGhcid dotGhciPath Nothing
67-- | Relative paths to local packages of an obelisk project
68-- TODO a way to query this
69getLocalPkgs :: Applicative f => f [FilePath]
70getLocalPkgs = pure ["backend", "common", "frontend"]
74 => FilePath -- ^ package directory
75 -> m (Maybe CabalPackageInfo)
76parseCabalPackage dir = do
77 let cabalFp = dir </> (takeBaseName dir <> ".cabal")
78 hpackFp = dir </> "package.yaml"
79 hasCabal <- liftIO $ doesFileExist cabalFp
80 hasHpack <- liftIO $ doesFileExist hpackFp
82 mCabalContents <- if hasCabal
83 then Just <$> liftIO (readUTF8File cabalFp)
86 let decodeOptions = DecodeOptions hpackFp Nothing decodeYaml
87 liftIO (readPackageConfig decodeOptions) >>= \case
89 putLog Error $ T.pack $ "Failed to parse " <> hpackFp <> ": " <> err
91 Right (DecodeResult hpackPackage _ _) -> do
92 return $ Just $ renderPackage [] hpackPackage
95 fmap join $ forM mCabalContents $ \cabalContents -> do
96 let (warnings, result) = runParseResult $ parseGenericPackageDescription $
97 toUTF8BS $ cabalContents
98 mapM_ (putLog Warning) $ fmap (T.pack . show) warnings
102 (_, lib) <- simplifyCondTree (const $ pure True) <$> condLibrary gpkg
103 pure $ CabalPackageInfo
104 { _cabalPackageInfo_packageRoot = takeDirectory cabalFp
105 , _cabalPackageInfo_sourceDirs =
106 fromMaybe (pure ".") $ NE.nonEmpty $ hsSourceDirs $ libBuildInfo lib
107 , _cabalPackageInfo_defaultExtensions =
108 defaultExtensions $ libBuildInfo lib
110 Left (_, errors) -> do
111 putLog Error $ T.pack $ "Failed to parse " <> cabalFp <> ":"
112 mapM_ (putLog Error) $ fmap (T.pack . show) errors
115withUTF8FileContentsM :: (MonadIO m, HasCliConfig e m) => FilePath -> (ByteString -> CliT e IO a) -> m a
116withUTF8FileContentsM fp f = do
118 liftIO $ withUTF8FileContents fp $ runCli c . f . toUTF8BS
120-- | Create ghci configuration to load the given packages
123 => [FilePath] -- ^ List of packages to load into ghci
124 -> (FilePath -> m ()) -- ^ Action to run with the path to generated temporory .ghci
126withGhciScript pkgs f = do
127 (pkgDirErrs, packageInfos) <- fmap partitionEithers $ forM pkgs $ \pkg -> do
128 flip fmap (parseCabalPackage pkg) $ \case
130 Just packageInfo -> Right packageInfo
132 when (null packageInfos) $
133 failWith $ T.pack $ "No valid pkgs found in " <> intercalate ", " pkgs
134 unless (null pkgDirErrs) $
135 putLog Warning $ T.pack $ "Failed to find pkgs in " <> intercalate ", " pkgDirErrs
137 let extensions = packageInfos >>= _cabalPackageInfo_defaultExtensions
138 extensionsLine = if extensions == mempty
140 else ":set " <> intercalate " " ((("-X" <>) . prettyShow) <$> extensions)
142 [ ":set -i" <> intercalate ":" (packageInfos >>= rootedSourceDirs)
144 , ":load Backend Frontend"
145 , "import Obelisk.Run"
146 , "import qualified Frontend"
147 , "import qualified Backend"
149 withSystemTempDirectory "ob-ghci" $ \fp -> do
150 let dotGhciPath = fp </> ".ghci"
151 liftIO $ writeFile dotGhciPath dotGhci
155 rootedSourceDirs pkg = NE.toList $
156 (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
161 => FilePath -- ^ Path to .ghci
163runGhciRepl dotGhci = inProjectShell "ghc" $ unwords $ "ghci" : ["-ghci-script", dotGhci]
168 => FilePath -- ^ Path to .ghci
169 -> Maybe String -- ^ Optional command to run at every reload
171runGhcid dotGhci mcmd = callCommand $ unwords $ "ghcid" : opts
175 --TODO: The decision of whether to use -fwarn-redundant-constraints should probably be made by the user
176 , "--command='ghci -Wall -ignore-dot-ghci -fwarn-redundant-constraints -ghci-script " <> dotGhci <> "' "
178 , "--outputfile=ghcid-output.txt"
180 testCmd = maybeToList (flip fmap mcmd $ \cmd -> "--test='" <> cmd <> "'")
182getFreePort :: MonadIO m => m PortNumber
183getFreePort = liftIO $ withSocketsDo $ do
184 addr:_ <- getAddrInfo (Just defaultHints) (Just "127.0.0.1") (Just "0")
185 bracket (open addr) close socketPort
188 sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
189 bind sock (addrAddress addr)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list