forked from commercialhaskell/stack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPagerEditor.hs
More file actions
129 lines (115 loc) · 4.94 KB
/
PagerEditor.hs
File metadata and controls
129 lines (115 loc) · 4.94 KB
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
{-# LANGUAGE ScopedTypeVariables, RankNTypes, DeriveDataTypeable #-}
-- | Run external pagers (@$PAGER@, @less@, @more@) and editors (@$VISUAL@,
-- @$EDITOR@, @nano@, @pico@, @vi@).
module System.Process.PagerEditor
(-- * Pager
pageWriter
,pageByteString
,pageBuilder
,pageFile
,pageString
,PagerException(..)
-- * Editor
,editFile
,editReaderWriter
,editByteString
,editString
,EditorException(..))
where
import Control.Exception (try,IOException,throwIO,Exception)
import Data.ByteString.Lazy (ByteString,hPut,readFile)
import Data.ByteString.Builder (Builder,stringUtf8,hPutBuilder)
import Data.Typeable (Typeable)
import System.Directory (findExecutable)
import System.Environment (lookupEnv)
import System.Exit (ExitCode(..))
import System.FilePath ((</>))
import System.Process (createProcess,shell,proc,waitForProcess,StdStream (CreatePipe)
,CreateProcess(std_in, close_fds, delegate_ctlc))
import System.IO (hClose,Handle,hPutStr,readFile,withFile,IOMode(WriteMode),stdout)
import System.IO.Temp (withSystemTempDirectory)
-- | Run pager, providing a function that writes to the pager's input.
pageWriter :: (Handle -> IO ()) -> IO ()
pageWriter writer =
do mpager <- lookupEnv "PAGER" `orElse`
findExecutable "less" `orElse`
findExecutable "more"
case mpager of
Just pager ->
do (Just h,_,_,procHandle) <- createProcess (shell pager)
{std_in = CreatePipe
,close_fds = True
,delegate_ctlc = True}
(_::Either IOException ()) <- try (do writer h
hClose h)
exit <- waitForProcess procHandle
case exit of
ExitSuccess -> return ()
ExitFailure n -> throwIO (PagerExitFailure pager n)
return ()
Nothing -> writer stdout
-- | Run pager to display a lazy ByteString.
pageByteString :: ByteString -> IO ()
pageByteString = pageWriter . flip hPut
-- | Run pager to display a ByteString-Builder.
pageBuilder :: Builder -> IO ()
pageBuilder = pageWriter . flip hPutBuilder
-- | Run pager to display contents of a file.
pageFile :: FilePath -> IO ()
pageFile p = pageByteString =<< Data.ByteString.Lazy.readFile p
-- | Run pager to display a string.
pageString :: String -> IO ()
pageString = pageBuilder . stringUtf8
-- | Run editor to edit a file.
editFile :: FilePath -> IO ()
editFile path =
do meditor <- lookupEnv "VISUAL" `orElse`
lookupEnv "EDITOR" `orElse`
findExecutable "nano" `orElse`
findExecutable "pico" `orElse`
findExecutable "vi"
case meditor of
Just editor ->
do (_,_,_,procHandle) <- createProcess (proc "sh" ["-c", editor ++ " \"$1\"", "sh", path])
{close_fds = True,delegate_ctlc = True}
exitCode <- waitForProcess procHandle
case exitCode of
ExitSuccess -> return ()
ExitFailure n -> throwIO (EditorExitFailure editor n)
Nothing -> throwIO EditorNotFound
-- | Run editor, providing functions to write and read the file contents.
editReaderWriter :: forall a. String -> (Handle -> IO ()) -> (FilePath -> IO a) -> IO a
editReaderWriter filename writer reader =
withSystemTempDirectory ""
(\p -> do let p' = p </> filename
withFile p' WriteMode writer
editFile p'
reader p')
-- | Run editor on a ByteString.
editByteString :: String -> ByteString -> IO ByteString
editByteString f s = editReaderWriter f (flip hPut s) Data.ByteString.Lazy.readFile
-- | Run editor on a String.
editString :: String -> String -> IO String
editString f s = editReaderWriter f (flip hPutStr s) System.IO.readFile
-- | Short-circuit first Just.
orElse :: (Monad m) => m (Maybe a) -> m (Maybe a) -> m (Maybe a)
orElse a b = do m <- a
case m of
Just _ -> return m
Nothing -> b
-- | Exception running pager.
data PagerException = PagerNotFound
| PagerExitFailure FilePath Int
deriving Typeable
instance Show PagerException where
show PagerNotFound = "No pager found (tried $PAGER, `less`, and `more`.)"
show (PagerExitFailure p n) = "Pager (`" ++ p ++ "') exited with non-zero status: " ++ show n
instance Exception PagerException
-- | Exception running editor.
data EditorException = EditorNotFound
| EditorExitFailure FilePath Int
deriving Typeable
instance Show EditorException where
show EditorNotFound = "No editor found (tried $VISUAL, $PAGER, `nano`, `pico`, and `vi`.)"
show (EditorExitFailure p n) = "Editor (`" ++ p ++ "') exited with non-zero status: " ++ show n
instance Exception EditorException