1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
-- | A Compiler that supports unix filters.
--
module Hakyll.Core.UnixFilter
( unixFilter
) where
import Control.Concurrent (forkIO)
import System.IO (hPutStr, hClose, hGetContents)
import System.Posix.Process (executeFile, forkProcess)
import System.Posix.IO ( dupTo, createPipe, stdInput
, stdOutput, closeFd, fdToHandle
)
import Hakyll.Core.Compiler
-- | Use a unix filter as compiler. For example, we could use the 'rev' program
-- as a compiler.
--
-- > rev :: Compiler Resource String
-- > rev = getResourceString >>> unixFilter "rev" []
--
-- A more realistic example: one can use this to call, for example, the sass
-- compiler on CSS files. More information about sass can be found here:
--
-- <http://sass-lang.com/>
--
-- The code is fairly straightforward, given that we use @.scss@ for sass:
--
-- > route "style.scss" $ setExtension "css"
-- > compile "style.scss" $
-- > getResourceString >>> unixFilter "sass" ["-s", "--scss"]
-- > >>> arr compressCss
--
unixFilter :: String -- ^ Program name
-> [String] -- ^ Program args
-> Compiler String String -- ^ Resulting compiler
unixFilter programName args =
timedCompiler ("Executing external program " ++ programName) $
unsafeCompiler $ \input -> unixFilterIO programName args input
-- | Internally used function
--
unixFilterIO :: String
-> [String]
-> String
-> IO String
unixFilterIO programName args input = do
-- Create pipes
(stdinRead, stdinWrite) <- createPipe
(stdoutRead, stdoutWrite) <- createPipe
-- Fork the child
_ <- forkProcess $ do
-- Copy our pipes over the regular stdin/stdout
_ <- dupTo stdinRead stdInput
_ <- dupTo stdoutWrite stdOutput
-- Close the now unneeded file descriptors in the child
mapM_ closeFd [stdinWrite, stdoutRead, stdinRead, stdoutWrite]
-- Execute the program
_ <- executeFile programName True args Nothing
return ()
-- On the parent side, close the client-side FDs.
mapM_ closeFd [stdinRead, stdoutWrite]
-- Write the input to the child pipe
_ <- forkIO $ do
stdinWriteHandle <- fdToHandle stdinWrite
hPutStr stdinWriteHandle input
hClose stdinWriteHandle
-- Receive the output from the child
stdoutReadHandle <- fdToHandle stdoutRead
hGetContents stdoutReadHandle
|