on ae@test-debug-menu help sign in
93 lines 4.4 kB history raw
1{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE OverloadedStrings #-}
4module Obelisk.Command.Preprocessor where
6import qualified Data.ByteString.Lazy as BL
7import qualified Data.ByteString.Lazy.Builder as BU
8import Data.List (intersperse, isPrefixOf, sortOn)
9import Data.Maybe (fromMaybe)
10import qualified Data.Text.Lazy.Builder as TL
11import qualified Data.Text.Lazy.Encoding as TL
12import Distribution.Compiler (CompilerFlavor (..))
13import Language.Haskell.Extension (Extension (..), Language(..))
14import System.IO (IOMode (..), hClose, hPutStrLn, openFile, stderr)
15import System.FilePath (hasTrailingPathSeparator, joinPath, normalise, splitPath)
17import Obelisk.Command.Run (CabalPackageInfo (..), parseCabalPackage')
19applyPackages :: FilePath -> FilePath -> FilePath -> [FilePath] -> IO ()
20applyPackages origPath inPath outPath packagePaths = do
21 -- This code is intended to be executed via ghci's -pgmF preprocessor option
22 -- The command line arguments are passed in via ghc, which dictates the first three options and meanings
23 -- In order for this code to execute, origPath must contain either a '.' character or a '/' character.
24 -- (This is to avoid the possibility of the command line syntax conflicting with another ob command)
25 -- We do have control over the remaining arguments, but they must be the same for all files.
26 -- Thus, the fourth command line argument must be "apply-packages", which has already been handled.
27 -- We assume all the remaining arguments passed in are paths to cabal or hpack package specifications.
29 outFile <- openFile outPath WriteMode
30 let hPutTextBuilder h = BU.hPutBuilder h . TL.encodeUtf8Builder . TL.toLazyText
32 -- putStr "--------------------------------------------------------------------------------\n"
33 -- print args
34 -- putStr "--------------------------------------------------------------------------------\n"
36 -- Thus we must select among the packagePaths for the file we are going to parse.
38 let takeDirs = takeWhile hasTrailingPathSeparator
39 packageDirs = sortOn (negate . length . takeDirs) $ map (splitPath . normalise) packagePaths
40 origDir = splitPath $ normalise origPath
41 matches = [ joinPath d | d <- packageDirs, takeDirs d `isPrefixOf` origDir ]
43 -- So the first element of matches is going to be the deepest path to a package spec that contains
44 -- our file as a subdirectory.
46 case matches of
47 [] -> hPutTextBuilder outFile (lineNumberPragma origPath) -- TODO: probably should produce a warning
48 (packagePath:_) -> do
49 parseCabalPackage' packagePath >>= \case
50 Left err ->
51 hPutStrLn stderr $ "Error: Unable to parse cabal package " <> packagePath <> "; Skipping preprocessor on " <> origPath <> ". Error was " <> show err
52 Right (_warnings, packageInfo) ->
53 hPutTextBuilder outFile (generateHeader origPath packageInfo)
55 BL.readFile inPath >>= BL.hPut outFile
56 hClose outFile
59 -- I'm pretty sure there's a certain amount of oversimplification in CabalPackageInfo, so I doubt this is fully robust.
61generateHeader :: FilePath -> CabalPackageInfo -> TL.Builder
62generateHeader origPath packageInfo =
63 hsExtensions <> ghcOptions <> lineNumberPragma origPath
64 where
65 hsExtensions =
66 if not (null extList)
67 then TL.fromText "{-# LANGUAGE "
68 <> mconcat (intersperse (TL.fromText ", ") extList)
69 <> TL.fromText " #-}\n"
70 else mempty
71 extList = addDefaultLanguage $ concatMap showExt $ _cabalPackageInfo_defaultExtensions packageInfo
72 addDefaultLanguage =
73 case _cabalPackageInfo_defaultLanguage packageInfo of
74 Nothing -> id
75 Just x -> case x of
76 UnknownLanguage ext -> ( TL.fromString ext :)
77 ext -> ( TL.fromString (show ext) :)
78 showExt = \case
79 EnableExtension ext -> [TL.fromString (show ext)]
80 DisableExtension _ -> []
81 UnknownExtension ext -> [TL.fromString ext]
83 ghcOptions =
84 if not (null optList)
85 then TL.fromText "{-# OPTIONS_GHC "
86 <> mconcat (intersperse (TL.fromText " ") optList)
87 <> TL.fromText " #-}\n"
88 else mempty
89 optList = map TL.fromString $ fromMaybe [] $ lookup GHC (_cabalPackageInfo_compilerOptions packageInfo)
91lineNumberPragma :: FilePath -> TL.Builder
92lineNumberPragma origPath =
93 TL.fromText "{-# LINE 1 \"" <> TL.fromString origPath <> TL.fromText "\" #-}\n"