-
Notifications
You must be signed in to change notification settings - Fork 148
Expand file tree
/
Copy pathElmFormat.hs
More file actions
395 lines (314 loc) · 13.2 KB
/
ElmFormat.hs
File metadata and controls
395 lines (314 loc) · 13.2 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
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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
{-# OPTIONS_GHC -Wall #-}
module ElmFormat where
import Elm.Utils ((|>))
import System.Exit (exitFailure, exitSuccess)
import Messages.Types
import Messages.Formatter.Format
import Control.Monad (when)
import Control.Monad.Free
import Data.Maybe (isJust)
import CommandLine.Helpers
import ElmVersion
import ElmFormat.FileStore (FileStore)
import ElmFormat.Operation (Operation)
import qualified AST.Module
import qualified Flags
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Char8 as Char8
import qualified Data.ByteString.Lazy as Lazy
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified ElmFormat.Execute as Execute
import qualified ElmFormat.Parse as Parse
import qualified ElmFormat.Render.Text as Render
import qualified ElmFormat.FileStore as FileStore
import qualified ElmFormat.Filesystem as FS
import qualified ElmFormat.Operation as Operation
import qualified ElmFormat.Version
import qualified Reporting.Error.Syntax as Syntax
import qualified Reporting.Result as Result
-- If elm-format was successful and formatted result differ
-- from original content, writes the results to the output file.
-- Otherwise, display errors and exit
writeResult
:: Operation f =>
ElmVersion
-> Int
-> Destination
-> FilePath
-> Text.Text
-> Result.Result () Syntax.Error AST.Module.Module
-> Free f (Maybe Bool)
writeResult elmVersion tabSize destination inputFile inputText result =
case result of
Result.Result _ (Result.Ok modu) ->
let
renderedText =
Render.render elmVersion tabSize modu
rendered =
renderedText
|> Text.encodeUtf8
in
case destination of
UpdateInPlace ->
Operation.deprecatedIO $
Char8.putStr rendered
>> return Nothing
ValidateOnly ->
if inputText /= renderedText then
onInfo (FileWouldChange inputFile)
>> return (Just False)
else
return $ Just True
ToFile path ->
let
shouldWriteToFile =
inputFile /= path || inputText /= renderedText
in
if shouldWriteToFile then
Operation.deprecatedIO $
ByteString.writeFile path rendered
>> return Nothing
else
return Nothing
Result.Result _ (Result.Err errs) ->
onInfo (ParseError inputFile (Text.unpack inputText) errs)
>> return (Just False)
processTextInput :: Operation f => ElmVersion -> Int -> Destination -> FilePath -> Text.Text -> Free f (Maybe Bool)
processTextInput elmVersion tabSize destination inputFile inputText =
Parse.parse inputText
|> writeResult elmVersion tabSize destination inputFile inputText
processFileInput :: Operation f => ElmVersion -> Int -> FilePath -> Destination -> Free f (Maybe Bool)
processFileInput elmVersion tabSize inputFile destination =
do
inputText <- Operation.deprecatedIO $ fmap Text.decodeUtf8 $ ByteString.readFile inputFile
processTextInput elmVersion tabSize destination inputFile inputText
resolveFile :: FileStore f => FilePath -> Free f (Either InputFileMessage [FilePath])
resolveFile path =
do
fileType <- FileStore.stat path
case fileType of
FileStore.IsFile ->
return $ Right [path]
FileStore.IsDirectory ->
do
elmFiles <- FS.findAllElmFiles path
case elmFiles of
[] -> return $ Left $ NoElmFiles path
_ -> return $ Right elmFiles
FileStore.DoesNotExist ->
return $ Left $ FileDoesNotExist path
collectErrors :: [Either l r] -> Either [l] [r]
collectErrors list =
let
step acc next =
case (next, acc) of
(Left l, Right _) ->
Left [l]
(Left l, Left ls) ->
Left (l : ls)
(Right r, Right rs) ->
Right (r : rs)
(Right _, Left ls) ->
Left ls
in
foldl step (Right []) list
resolveFiles :: FileStore f => [FilePath] -> Free f (Either [InputFileMessage] [FilePath])
resolveFiles inputFiles =
do
result <- collectErrors <$> mapM resolveFile inputFiles
case result of
Left ls ->
return $ Left ls
Right files ->
return $ Right $ concat files
handleFilesInput :: Operation f => ElmVersion -> Int -> [FilePath] -> Maybe FilePath -> Bool -> Bool -> Free f (Maybe Bool)
handleFilesInput elmVersion tabSize inputFiles outputFile autoYes validateOnly =
do
elmFiles <- resolveFiles inputFiles
case elmFiles of
Left errors ->
Operation.deprecatedIO $
do
putStrLn $ r $ BadInputFiles errors
exitFailure
Right [inputFile] -> do
realOutputFile <- decideOutputFile autoYes inputFile outputFile
case realOutputFile of
Nothing ->
return Nothing
Just realOutputFile' ->
do
let destination = if validateOnly then ValidateOnly else ToFile realOutputFile'
onInfo $ ProcessingFiles [inputFile]
processFileInput elmVersion tabSize inputFile destination
Right elmFiles -> do
when (isJust outputFile)
exitOnInputDirAndOutput
canOverwriteFiles <- getApproval autoYes elmFiles
if canOverwriteFiles
then
let
merge prev next =
case (prev, next) of
(Nothing, Just b) -> Just b
(Just b, Nothing) -> Just b
(Just a, Just b) -> Just $ a && b
(Nothing, Nothing) -> Nothing
dst file =
if validateOnly then
ValidateOnly
else
ToFile file
in
do
onInfo $ ProcessingFiles elmFiles
validationResults <- mapM (\file -> processFileInput elmVersion tabSize file (dst file)) elmFiles
return $ foldl merge Nothing validationResults
else
return Nothing
data WhatToDo
= FormatToFile FilePath FilePath
| StdinToFile FilePath
| FormatInPlace FilePath [FilePath]
| StdinToStdout
| Validate Source
data Source
= Stdin
| FromFiles FilePath [FilePath]
data Destination
= ValidateOnly
| UpdateInPlace
| ToFile FilePath
determineSource :: Bool -> [FilePath] -> Either ErrorMessage Source
determineSource stdin inputFiles =
case ( stdin, inputFiles ) of
( True, [] ) -> Right Stdin
( False, [] ) -> Left NoInputs
( False, first:rest ) -> Right $ FromFiles first rest
( True, _:_ ) -> Left TooManyInputs
determineDestination :: Maybe FilePath -> Bool -> Either ErrorMessage Destination
determineDestination output validate =
case ( output, validate ) of
( Nothing, True ) -> Right ValidateOnly
( Nothing, False ) -> Right UpdateInPlace
( Just path, False ) -> Right $ ToFile path
( Just _, True ) -> Left OutputAndValidate
determineWhatToDo :: Source -> Destination -> Either ErrorMessage WhatToDo
determineWhatToDo source destination =
case ( source, destination ) of
( _, ValidateOnly ) -> Right $ Validate source
( Stdin, UpdateInPlace ) -> Right StdinToStdout
( Stdin, ToFile output ) -> Right $ StdinToFile output
( FromFiles first [], ToFile output ) -> Right $ FormatToFile first output
( FromFiles first rest, UpdateInPlace ) -> Right $ FormatInPlace first rest
( FromFiles _ _, ToFile _ ) -> Left SingleOutputWithMultipleInputs
determineWhatToDoFromConfig :: Flags.Config -> Either ErrorMessage WhatToDo
determineWhatToDoFromConfig config =
do
source <- determineSource (Flags._stdin config) (Flags._input config)
destination <- determineDestination (Flags._output config) (Flags._validate config)
determineWhatToDo source destination
validate :: Operation f => ElmVersion -> Int -> Source -> Free f Bool
validate elmVersion tabSize source =
do
result <-
case source of
Stdin ->
do
input <- Operation.deprecatedIO Lazy.getContents
Lazy.toStrict input
|> Text.decodeUtf8
|> processTextInput elmVersion tabSize ValidateOnly "<STDIN>"
FromFiles first rest ->
handleFilesInput elmVersion tabSize (first:rest) Nothing True True
case result of
Nothing ->
error "Validation should always give a result"
Just isSuccess ->
return isSuccess
exitWithError :: ErrorMessage -> IO ()
exitWithError message =
(putStrLn $ r $ message)
>> exitFailure
determineVersion :: ElmVersion -> Bool -> Either ErrorMessage ElmVersion
determineVersion elmVersion upgrade =
case (elmVersion, upgrade) of
(Elm_0_18, True) ->
Right Elm_0_18_Upgrade
(_, True) ->
Left $ MustSpecifyVersionWithUpgrade Elm_0_18_Upgrade
(_, False) ->
Right elmVersion
exit :: Bool -> IO ()
exit True = exitSuccess
exit False = exitFailure
elmFormatVersion :: String
elmFormatVersion =
ElmFormat.Version.asString
experimental :: Maybe String
experimental =
ElmFormat.Version.experimental
main :: ElmVersion -> IO ()
main defaultVersion =
do
config <- Flags.parse defaultVersion elmFormatVersion experimental
let autoYes = Flags._yes config
let tabSize = Flags._tabSize config
let elmVersionResult = determineVersion (Flags._elmVersion config) (Flags._upgrade config)
case (elmVersionResult, determineWhatToDoFromConfig config) of
(_, Left NoInputs) ->
Flags.showHelpText defaultVersion elmFormatVersion experimental
>> exitFailure
(_, Left message) ->
exitWithError message
(Left message, _) ->
exitWithError message
(Right elmVersion, Right (Validate source)) ->
do
isSuccess <-
validate elmVersion tabSize source
|> Execute.run (Execute.forMachine elmVersion)
exit isSuccess
(Right elmVersion, Right (FormatInPlace first rest)) ->
do
result <- foldFree Execute.forHuman $ handleFilesInput elmVersion tabSize (first:rest) Nothing autoYes False
case result of
Just False ->
exitFailure
_ ->
exitSuccess
(Right elmVersion, Right (FormatToFile input output)) ->
do
result <- foldFree Execute.forHuman $ handleFilesInput elmVersion tabSize [input] (Just output) autoYes False
case result of
Just False ->
exitFailure
_ ->
exitSuccess
(Right elmVersion, Right StdinToStdout) ->
do
input <- Lazy.getContents
result <-
Lazy.toStrict input
|> Text.decodeUtf8
|> processTextInput elmVersion tabSize UpdateInPlace "<STDIN>"
|> foldFree Execute.forHuman
case result of
Just False ->
exitFailure
_ ->
exitSuccess
(Right elmVersion, Right (StdinToFile output)) ->
do
input <- Lazy.getContents
result <-
Lazy.toStrict input
|> Text.decodeUtf8
|> processTextInput elmVersion tabSize (ToFile output) "<STDIN>"
|> foldFree Execute.forHuman
case result of
Just False ->
exitFailure
_ ->
exitSuccess