never executed always true always false
1 module CommandLine.TransformFiles
2 ( Result
3 , TransformMode(..), applyTransformation
4 , ValidateMode(..), validateNoChanges
5 ) where
6
7 -- This module provides reusable functions for command line tools that
8 -- transform files.
9
10 import CommandLine.InfoFormatter (ExecuteMode(..))
11 import qualified CommandLine.InfoFormatter as InfoFormatter
12 import CommandLine.World (World)
13 import qualified CommandLine.World as World
14 import Control.Monad.State hiding (runState)
15 import Data.Text (Text)
16
17
18
19 data Result a
20 = NoChange FilePath a
21 | Changed FilePath a
22
23
24 checkChange :: Eq a => (FilePath, a) -> a -> Result a
25 checkChange (inputFile, inputText) outputText =
26 if inputText == outputText
27 then NoChange inputFile outputText
28 else Changed inputFile outputText
29
30
31 updateFile :: World m => Result Text -> m ()
32 updateFile result =
33 case result of
34 NoChange _ _ -> return ()
35 Changed outputFile outputText -> World.writeUtf8File outputFile outputText
36
37
38 readStdin :: World m => m (FilePath, Text)
39 readStdin =
40 (,) "<STDIN>" <$> World.getStdin
41
42
43 readFromFile :: World m => (FilePath -> StateT s m ()) -> FilePath -> StateT s m (FilePath, Text)
44 readFromFile onProcessingFile filePath =
45 onProcessingFile filePath
46 *> lift (World.readUtf8FileWithPath filePath)
47
48
49 data TransformMode
50 = StdinToStdout
51 | StdinToFile FilePath
52 | FileToStdout FilePath
53 | FileToFile FilePath FilePath
54 | FilesInPlace FilePath [FilePath]
55
56
57 applyTransformation ::
58 World m =>
59 InfoFormatter.Loggable info =>
60 InfoFormatter.ToConsole prompt =>
61 (FilePath -> info)
62 -> Bool
63 -> ([FilePath] -> prompt)
64 -> ((FilePath, Text) -> Either info Text)
65 -> TransformMode
66 -> m Bool
67 applyTransformation processingFile autoYes confirmPrompt transform mode =
68 let
69 usesStdout =
70 case mode of
71 StdinToStdout -> True
72 StdinToFile _ -> True
73 FileToStdout _ -> True
74 FileToFile _ _ -> False
75 FilesInPlace _ _ -> False
76
77 infoMode = ForHuman usesStdout
78
79 onInfo = InfoFormatter.onInfo infoMode
80
81 approve = InfoFormatter.approve infoMode autoYes . confirmPrompt
82 in
83 runState (InfoFormatter.init infoMode) (InfoFormatter.done infoMode) $
84 case mode of
85 StdinToStdout ->
86 lift (transform <$> readStdin) >>= logErrorOr onInfo (lift . World.writeStdout)
87
88 StdinToFile outputFile ->
89 lift (transform <$> readStdin) >>= logErrorOr onInfo (lift . World.writeUtf8File outputFile)
90
91 FileToStdout inputFile ->
92 lift (transform <$> World.readUtf8FileWithPath inputFile) >>= logErrorOr onInfo (lift . World.writeStdout)
93
94 FileToFile inputFile outputFile ->
95 (transform <$> readFromFile (onInfo . processingFile) inputFile) >>= logErrorOr onInfo (lift . World.writeUtf8File outputFile)
96
97 FilesInPlace first rest ->
98 do
99 canOverwrite <- lift $ approve (first:rest)
100 if canOverwrite
101 then all id <$> mapM formatFile (first:rest)
102 else return True
103 where
104 formatFile file = ((\i -> checkChange i <$> transform i) <$> readFromFile (onInfo . processingFile) file) >>= logErrorOr onInfo (lift . updateFile)
105
106
107 data ValidateMode
108 = ValidateStdin
109 | ValidateFiles FilePath [FilePath]
110
111
112 validateNoChanges ::
113 World m =>
114 InfoFormatter.Loggable info =>
115 (FilePath -> info)
116 -> ((FilePath, Text) -> Either info ())
117 -> ValidateMode
118 -> m Bool
119 validateNoChanges processingFile validate mode =
120 let
121 infoMode = ForMachine
122 onInfo = InfoFormatter.onInfo infoMode
123 in
124 runState (InfoFormatter.init infoMode) (InfoFormatter.done infoMode) $
125 case mode of
126 ValidateStdin ->
127 lift (validate <$> readStdin) >>= logError onInfo
128
129 ValidateFiles first rest ->
130 and <$> mapM validateFile (first:rest)
131 where
132 validateFile file =
133 (validate <$> readFromFile (onInfo . processingFile) file)
134 >>= logError onInfo
135
136
137 logErrorOr :: Monad m => (error -> m ()) -> (a -> m ()) -> Either error a -> m Bool
138 logErrorOr onInfo fn result =
139 case result of
140 Left message ->
141 onInfo message *> return False
142
143 Right value ->
144 fn value *> return True
145
146 logError :: Monad m => (error -> m ()) -> Either error () -> m Bool
147 logError onInfo =
148 logErrorOr onInfo return
149
150
151
152 runState :: Monad m => (m (), state) -> (state -> m ()) -> StateT state m result -> m result
153 runState (initM, initialState) done run =
154 do
155 initM
156 (result, finalState) <- runStateT run initialState
157 done finalState
158 return result