forked from elm/compiler
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFile.hs
231 lines (166 loc) · 5.59 KB
/
File.hs
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
module File
( Time
, getTime
, zeroTime
, writeBinary
, readBinary
, writeUtf8
, readUtf8
, writeBuilder
, writePackage
, exists
, remove
, removeDir
)
where
import qualified Codec.Archive.Zip as Zip
import Control.Exception (catch)
import qualified Data.Binary as Binary
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Lazy as LBS
import qualified Data.Fixed as Fixed
import qualified Data.List as List
import qualified Data.Time.Clock as Time
import qualified Data.Time.Clock.POSIX as Time
import qualified Foreign.ForeignPtr as FPtr
import GHC.IO.Exception (IOException, IOErrorType(InvalidArgument))
import qualified System.Directory as Dir
import qualified System.FilePath as FP
import System.FilePath ((</>))
import qualified System.IO as IO
import System.IO.Error (ioeGetErrorType, annotateIOError, modifyIOError)
-- TIME
newtype Time = Time Fixed.Pico
deriving (Eq, Ord)
getTime :: FilePath -> IO Time
getTime path =
fmap
(Time . Time.nominalDiffTimeToSeconds . Time.utcTimeToPOSIXSeconds)
(Dir.getModificationTime path)
zeroTime :: Time
zeroTime =
Time 0
instance Binary.Binary Time where
put (Time time) = Binary.put time
get = Time <$> Binary.get
-- BINARY
writeBinary :: (Binary.Binary a) => FilePath -> a -> IO ()
writeBinary path value =
do let dir = FP.dropFileName path
Dir.createDirectoryIfMissing True dir
Binary.encodeFile path value
readBinary :: (Binary.Binary a) => FilePath -> IO (Maybe a)
readBinary path =
do pathExists <- Dir.doesFileExist path
if pathExists
then
do result <- Binary.decodeFileOrFail path
case result of
Right a ->
return (Just a)
Left (offset, message) ->
do IO.hPutStrLn IO.stderr $ unlines $
[ "+-------------------------------------------------------------------------------"
, "| Corrupt File: " ++ path
, "| Byte Offset: " ++ show offset
, "| Message: " ++ message
, "|"
, "| Please report this to https://github.com/elm/compiler/issues"
, "| Trying to continue anyway."
, "+-------------------------------------------------------------------------------"
]
return Nothing
else
return Nothing
-- WRITE UTF-8
writeUtf8 :: FilePath -> BS.ByteString -> IO ()
writeUtf8 path content =
withUtf8 path IO.WriteMode $ \handle ->
BS.hPut handle content
withUtf8 :: FilePath -> IO.IOMode -> (IO.Handle -> IO a) -> IO a
withUtf8 path mode callback =
IO.withFile path mode $ \handle ->
do IO.hSetEncoding handle IO.utf8
callback handle
-- READ UTF-8
readUtf8 :: FilePath -> IO BS.ByteString
readUtf8 path =
withUtf8 path IO.ReadMode $ \handle ->
modifyIOError (encodingError path) $
do fileSize <- catch (IO.hFileSize handle) useZeroIfNotRegularFile
let readSize = max 0 (fromIntegral fileSize) + 1
hGetContentsSizeHint handle readSize (max 255 readSize)
useZeroIfNotRegularFile :: IOException -> IO Integer
useZeroIfNotRegularFile _ =
return 0
hGetContentsSizeHint :: IO.Handle -> Int -> Int -> IO BS.ByteString
hGetContentsSizeHint handle =
readChunks []
where
readChunks chunks readSize incrementSize =
do fp <- BS.mallocByteString readSize
readCount <- FPtr.withForeignPtr fp $ \buf -> IO.hGetBuf handle buf readSize
let chunk = BS.PS fp 0 readCount
if readCount < readSize && readSize > 0
then return $! BS.concat (reverse (chunk:chunks))
else readChunks (chunk:chunks) incrementSize (min 32752 (readSize + incrementSize))
encodingError :: FilePath -> IOError -> IOError
encodingError path ioErr =
case ioeGetErrorType ioErr of
InvalidArgument ->
annotateIOError
(userError "Bad encoding; the file must be valid UTF-8")
""
Nothing
(Just path)
_ ->
ioErr
-- WRITE BUILDER
writeBuilder :: FilePath -> B.Builder -> IO ()
writeBuilder path builder =
IO.withBinaryFile path IO.WriteMode $ \handle ->
do IO.hSetBuffering handle (IO.BlockBuffering Nothing)
B.hPutBuilder handle builder
-- WRITE PACKAGE
writePackage :: FilePath -> Zip.Archive -> IO ()
writePackage destination archive =
case Zip.zEntries archive of
[] ->
return ()
entry:entries ->
do let root = length (Zip.eRelativePath entry)
mapM_ (writeEntry destination root) entries
writeEntry :: FilePath -> Int -> Zip.Entry -> IO ()
writeEntry destination root entry =
let
path = drop root (Zip.eRelativePath entry)
in
if List.isPrefixOf "src/" path
|| path == "LICENSE"
|| path == "README.md"
|| path == "elm.json"
then
if not (null path) && last path == '/'
then Dir.createDirectoryIfMissing True (destination </> path)
else LBS.writeFile (destination </> path) (Zip.fromEntry entry)
else
return ()
-- EXISTS
exists :: FilePath -> IO Bool
exists path =
Dir.doesFileExist path
-- REMOVE FILES
remove :: FilePath -> IO ()
remove path =
do exists_ <- Dir.doesFileExist path
if exists_
then Dir.removeFile path
else return ()
removeDir :: FilePath -> IO ()
removeDir path =
do exists_ <- Dir.doesDirectoryExist path
if exists_
then Dir.removeDirectoryRecursive path
else return ()