never executed always true always false
1 {-# LANGUAGE DataKinds #-}
2 module ElmFormat.Cli (main, main') where
3
4 import Prelude ()
5 import Relude hiding (exitFailure, exitSuccess, putStr, putStrLn)
6
7 import AST.Module (Module)
8 import AST.Structure
9 import AST.V0_16
10 import CommandLine.Program (ProgramIO)
11 import CommandLine.TransformFiles (TransformMode(..), ValidateMode(..))
12 import CommandLine.World
13 import ElmFormat.Messages
14 import ElmVersion
15 import Reporting.Annotation (Located)
16
17 import qualified CommandLine.Program as Program
18 import qualified CommandLine.ResolveFiles as ResolveFiles
19 import qualified CommandLine.TransformFiles as TransformFiles
20 import qualified Data.Text as Text
21 import qualified ElmFormat.CliFlags as Flags
22 import qualified ElmFormat.Parse as Parse
23 import qualified ElmFormat.Render.Text as Render
24 import qualified ElmFormat.Version
25 import qualified Reporting.Result as Result
26 import qualified ElmFormat.AST.PublicAST as PublicAST
27 import qualified Data.Aeson as Aeson
28 import qualified Data.ByteString.Char8 as B
29 import qualified Data.ByteString.Lazy.Char8 as LB
30
31
32 data WhatToDo
33 = Format TransformMode
34 | ConvertToJson TransformMode
35 | ConvertFromJson TransformMode
36 | Validate ValidateMode
37
38
39 data Source
40 = Stdin
41 | FromFiles FilePath [FilePath]
42
43
44 data Destination
45 = InPlace
46 | ToFile FilePath
47
48
49 data Mode
50 = FormatMode
51 | JsonMode Flags.JsonMode
52 | ValidateMode
53
54
55 determineSource :: Bool -> Either [ResolveFiles.Error] [FilePath] -> Either ErrorMessage Source
56 determineSource stdin inputFiles =
57 case ( stdin, inputFiles ) of
58 ( _, Left fileErrors ) -> Left $ BadInputFiles fileErrors
59 ( True, Right [] ) -> Right Stdin
60 ( False, Right [] ) -> Left NoInputs
61 ( False, Right (first:rest) ) -> Right $ FromFiles first rest
62 ( True, Right (_:_) ) -> Left TooManyInputs
63
64
65 determineDestination :: Maybe FilePath -> Either ErrorMessage Destination
66 determineDestination output =
67 case output of
68 Nothing -> Right InPlace
69 Just path -> Right $ ToFile path
70
71
72 determineMode :: Bool -> Maybe Flags.JsonMode -> Either ErrorMessage Mode
73 determineMode doValidate json =
74 case ( doValidate, json ) of
75 ( False, Nothing ) -> Right FormatMode
76 ( True, Nothing ) -> Right ValidateMode
77 ( False, Just jsonMode ) -> Right (JsonMode jsonMode)
78 _ -> Left OutputAndValidate
79
80
81 determineWhatToDo :: Source -> Destination -> Mode -> Either ErrorMessage WhatToDo
82 determineWhatToDo source destination mode =
83 case ( mode, source, destination ) of
84 ( ValidateMode, _, ToFile _) -> Left OutputAndValidate
85 ( ValidateMode, Stdin, _ ) -> Right $ Validate ValidateStdin
86 ( ValidateMode, FromFiles first rest, _) -> Right $ Validate (ValidateFiles first rest)
87 ( FormatMode, Stdin, InPlace ) -> Right $ Format StdinToStdout
88 ( FormatMode, Stdin, ToFile output ) -> Right $ Format (StdinToFile output)
89 ( FormatMode, FromFiles first [], ToFile output ) -> Right $ Format (FileToFile first output)
90 ( FormatMode, FromFiles first rest, InPlace ) -> Right $ Format (FilesInPlace first rest)
91 ( JsonMode Flags.ElmToJson, Stdin, InPlace ) -> Right $ ConvertToJson StdinToStdout
92 ( JsonMode Flags.ElmToJson, Stdin, ToFile output ) -> Right $ ConvertToJson (StdinToFile output)
93 ( JsonMode Flags.ElmToJson, FromFiles first [], InPlace ) -> Right $ ConvertToJson (FileToStdout first)
94 ( JsonMode Flags.ElmToJson, FromFiles _ (_:_), _ ) -> error "TODO: --json with multiple files is not supported"
95 ( JsonMode Flags.JsonToElm, Stdin, InPlace ) -> Right $ ConvertFromJson StdinToStdout
96 ( JsonMode Flags.JsonToElm, Stdin, ToFile output ) -> Right $ ConvertFromJson (StdinToFile output)
97 ( JsonMode Flags.JsonToElm, FromFiles first [], InPlace ) -> Right $ ConvertFromJson (FileToStdout first)
98 ( JsonMode Flags.JsonToElm, FromFiles _ (_:_), _ ) -> error "TODO: --from-json with multiple files is not supported"
99 ( _, FromFiles _ _, ToFile _ ) -> Left SingleOutputWithMultipleInputs
100
101
102 determineWhatToDoFromConfig :: Flags.Config -> Either [ResolveFiles.Error] [FilePath] -> Either ErrorMessage WhatToDo
103 determineWhatToDoFromConfig config resolvedInputFiles =
104 do
105 source <- determineSource (Flags._stdin config) resolvedInputFiles
106 destination <- determineDestination (Flags._output config)
107 mode <- determineMode (Flags._validate config) (Flags._json config)
108 determineWhatToDo source destination mode
109
110
111 main :: World m => [String] -> m ()
112 main =
113 main'
114 ElmFormat.Version.asString
115 ElmFormat.Version.experimental
116
117 main' :: World m => String -> Maybe String -> [String] -> m ()
118 main' elmFormatVersion experimental args =
119 Program.run (Flags.parser elmFormatVersion experimental) run' args
120 where
121 run' :: World m => Flags.Config -> ProgramIO m ErrorMessage ()
122 run' flags =
123 do
124 resolvedInputFiles <- Program.liftM $ ResolveFiles.resolveElmFiles (Flags._input flags)
125
126 whatToDo <- case determineWhatToDoFromConfig flags resolvedInputFiles of
127 Left NoInputs -> Program.showUsage
128 Left err -> Program.error err
129 Right a -> return a
130
131 elmVersion <- case Flags._elmVersion flags of
132 Just v -> return v
133 Nothing -> Program.liftME autoDetectElmVersion
134
135 let autoYes = Flags._yes flags
136 result <- Program.liftM $ doIt elmVersion autoYes whatToDo
137 if result
138 then return ()
139 else Program.failed
140
141
142 autoDetectElmVersion :: World m => m (Either x ElmVersion)
143 autoDetectElmVersion =
144 do
145 hasElmPackageJson <- doesFileExist "elm-package.json"
146 if hasElmPackageJson
147 then
148 do
149 hasElmJson <- doesFileExist "elm.json"
150 if hasElmJson
151 then return $ Right Elm_0_19
152 else return $ Right Elm_0_18
153 else return $ Right Elm_0_19
154
155
156 validate :: ElmVersion -> (FilePath, Text.Text) -> Either InfoMessage ()
157 validate elmVersion input@(inputFile, inputText) =
158 case parseModule elmVersion input of
159 Right modu ->
160 if inputText /= Render.render elmVersion modu then
161 Left $ FileWouldChange elmVersion inputFile
162 else
163 Right ()
164
165 Left err ->
166 Left err
167
168
169 parseModule ::
170 ElmVersion
171 -> (FilePath, Text.Text)
172 -> Either InfoMessage (Module [UppercaseIdentifier] (ASTNS Located [UppercaseIdentifier] 'TopLevelNK))
173 parseModule elmVersion (inputFile, inputText) =
174 case Parse.parse elmVersion inputText of
175 Result.Result _ (Result.Ok modu) ->
176 Right modu
177
178 Result.Result _ (Result.Err errs) ->
179 Left $ ParseError inputFile errs
180
181
182 parseJson :: (FilePath, Text.Text)
183 -> Either InfoMessage (Module [UppercaseIdentifier] (ASTNS Identity [UppercaseIdentifier] 'TopLevelNK))
184 parseJson (inputFile, inputText) =
185 case Aeson.eitherDecode (LB.fromChunks . return . encodeUtf8 $ inputText) of
186 Right modu -> Right $ PublicAST.toModule modu
187
188 Left message ->
189 Left $ JsonParseError inputFile (Text.pack message)
190
191
192 format :: ElmVersion -> (FilePath, Text.Text) -> Either InfoMessage Text.Text
193 format elmVersion input =
194 Render.render elmVersion <$> parseModule elmVersion input
195
196
197 toJson :: ElmVersion -> (FilePath, Text.Text) -> Either InfoMessage Text.Text
198 toJson elmVersion (inputFile, inputText) =
199 let
200 config =
201 PublicAST.Config
202 { PublicAST.showSourceLocation = True
203 }
204 in
205 decodeUtf8 . B.concat . LB.toChunks . Aeson.encode . PublicAST.fromModule config
206 <$> parseModule elmVersion (inputFile, inputText)
207
208
209 fromJson :: ElmVersion -> (FilePath, Text.Text) -> Either InfoMessage Text.Text
210 fromJson elmVersion input =
211 Render.render elmVersion <$> parseJson input
212
213
214 doIt :: World m => ElmVersion -> Bool -> WhatToDo -> m Bool
215 doIt elmVersion autoYes whatToDo =
216 case whatToDo of
217 Validate validateMode ->
218 TransformFiles.validateNoChanges
219 ProcessingFile
220 (validate elmVersion)
221 validateMode
222
223 Format transformMode ->
224 TransformFiles.applyTransformation
225 ProcessingFile autoYes FilesWillBeOverwritten
226 (format elmVersion)
227 transformMode
228
229 ConvertToJson transformMode ->
230 TransformFiles.applyTransformation
231 ProcessingFile autoYes FilesWillBeOverwritten
232 (toJson elmVersion)
233 transformMode
234
235 ConvertFromJson transformMode ->
236 TransformFiles.applyTransformation
237 ProcessingFile autoYes FilesWillBeOverwritten
238 (fromJson elmVersion)
239 transformMode