{- |
Module      :  Camfort.Input
Description :  Handles input of code base and passing the files on to core functionality.
Copyright   :  Copyright 2017, Dominic Orchard, Andrew Rice, Mistral Contrastin, Matthew Danish
License     :  Apache-2.0

Maintainer  :  dom.orchard@gmail.com
-}

module Camfort.Input
  (
    -- * Classes
    Default(..)
    -- * Datatypes and Aliases
  , ProgramFile
  , AnalysisProgram
  , AnalysisRunner
  , AnalysisRunnerP
  , AnalysisRunnerConsumer
    -- * Builders for analysers and refactorings
  , runPerFileAnalysisP
  , runMultiFileAnalysis
  , describePerFileAnalysisP
  , doRefactor
  , doRefactorAndCreate
  , perFileRefactoring
    -- * Source directory and file handling
  , readParseSrcDir
  , loadModAndProgramFiles
    -- * Combinators
  , runThen
  ) where

import           Control.Monad.IO.Class
import qualified Data.ByteString.Char8         as B
import           Data.Either                   (partitionEithers)
import           Data.List                     (intercalate)

import           Control.Lens
import           Control.DeepSeq

import qualified Language.Fortran.AST          as F
import           Language.Fortran.Util.ModFile (ModFiles, emptyModFiles)
import           Language.Fortran.ParserMonad  (FortranVersion(..))

import           Camfort.Analysis
import           Camfort.Analysis.Annotations
import           Camfort.Analysis.Logger
import           Camfort.Analysis.ModFile      (MFCompiler, genModFiles, readParseSrcDir)
import           Camfort.Helpers
import           Camfort.Output

import           Pipes
import qualified Pipes.Prelude                 as P

-- | An analysis program which accepts inputs of type @a@ and produces results
-- of type @b@.
--
-- Has error messages of type @e@ and warnings of type @w@. Runs in the base
-- monad @m@.
type AnalysisProgram e w m a b = a -> AnalysisT e w m b

-- | An 'AnalysisRunner' is a function to run an 'AnalysisProgram' in a
-- particular way. Produces a final result of type @r@.
type AnalysisRunner e w m a b r =
  AnalysisProgram e w m a b -> LogOutput m -> LogLevel -> Bool -> ModFiles -> [(ProgramFile, SourceText)] -> m r

type AnalysisRunnerP e w m a b r =
  AnalysisProgram e w m a b -> LogOutput m -> LogLevel -> Bool -> ModFiles -> Pipe (ProgramFile, SourceText) r m ()

type AnalysisRunnerConsumer e w m a b r =
  AnalysisProgram e w m a b -> LogOutput m -> LogLevel -> Bool -> ModFiles -> Consumer (ProgramFile, SourceText) m ()

--------------------------------------------------------------------------------
--  Simple runners
--------------------------------------------------------------------------------

-- | Given an analysis program for a single file, run it over every input file
-- and collect the reports. Doesn't produce any output.
runPerFileAnalysisP
  :: (MonadIO m, Describe e, Describe w, NFData e, NFData w, NFData b)
  => AnalysisRunnerP e w m ProgramFile b (AnalysisReport e w b)
runPerFileAnalysisP :: AnalysisRunnerP e w m ProgramFile b (AnalysisReport e w b)
runPerFileAnalysisP AnalysisProgram e w m ProgramFile b
program LogOutput m
logOutput LogLevel
logLevel Bool
_ ModFiles
modFiles =
  ((ProgramFile, SourceText) -> m (AnalysisReport e w b))
-> Pipe (ProgramFile, SourceText) (AnalysisReport e w b) m ()
forall (m :: * -> *) a b r. Monad m => (a -> m b) -> Pipe a b m r
P.mapM (((ProgramFile, SourceText) -> m (AnalysisReport e w b))
 -> Pipe (ProgramFile, SourceText) (AnalysisReport e w b) m ())
-> ((ProgramFile, SourceText) -> m (AnalysisReport e w b))
-> Pipe (ProgramFile, SourceText) (AnalysisReport e w b) m ()
forall a b. (a -> b) -> a -> b
$ \ (ProgramFile
pf, SourceText
_) -> do
    -- liftIO . putStrLn $ "Running analysis on " ++ (F.pfGetFilename pf)
    FilePath
-> LogOutput m
-> LogLevel
-> ModFiles
-> AnalysisT e w m b
-> m (AnalysisReport e w b)
forall (m :: * -> *) e w a.
(Monad m, Describe e, Describe w) =>
FilePath
-> LogOutput m
-> LogLevel
-> ModFiles
-> AnalysisT e w m a
-> m (AnalysisReport e w a)
runAnalysisT (ProgramFile -> FilePath
forall a. ProgramFile a -> FilePath
F.pfGetFilename ProgramFile
pf)
                 LogOutput m
logOutput
                 LogLevel
logLevel
                 ModFiles
modFiles
                 (AnalysisProgram e w m ProgramFile b
program ProgramFile
pf)

-- | Run an analysis program over every input file and get the report. Doesn't
-- produce any output.
runMultiFileAnalysis
  :: (Monad m, Describe e, Describe w)
  => AnalysisRunner e w m [ProgramFile] b (AnalysisReport e w b)
runMultiFileAnalysis :: AnalysisRunner e w m [ProgramFile] b (AnalysisReport e w b)
runMultiFileAnalysis AnalysisProgram e w m [ProgramFile] b
program LogOutput m
logOutput LogLevel
logLevel Bool
_ ModFiles
modFiles
  = FilePath
-> LogOutput m
-> LogLevel
-> ModFiles
-> AnalysisT e w m b
-> m (AnalysisReport e w b)
forall (m :: * -> *) e w a.
(Monad m, Describe e, Describe w) =>
FilePath
-> LogOutput m
-> LogLevel
-> ModFiles
-> AnalysisT e w m a
-> m (AnalysisReport e w a)
runAnalysisT FilePath
"<unknown>" LogOutput m
logOutput LogLevel
logLevel ModFiles
modFiles (AnalysisT e w m b -> m (AnalysisReport e w b))
-> ([(ProgramFile, SourceText)] -> AnalysisT e w m b)
-> [(ProgramFile, SourceText)]
-> m (AnalysisReport e w b)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AnalysisProgram e w m [ProgramFile] b
program AnalysisProgram e w m [ProgramFile] b
-> ([(ProgramFile, SourceText)] -> [ProgramFile])
-> [(ProgramFile, SourceText)]
-> AnalysisT e w m b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((ProgramFile, SourceText) -> ProgramFile)
-> [(ProgramFile, SourceText)] -> [ProgramFile]
forall a b. (a -> b) -> [a] -> [b]
map (ProgramFile, SourceText) -> ProgramFile
forall a b. (a, b) -> a
fst

--------------------------------------------------------------------------------
--  Complex Runners
--------------------------------------------------------------------------------

-- doCreateBinary
--   :: (MonadIO m, Describe r, Describe w, Describe e)
--   => Text -> AnalysisRunner e w m ProgramFile r ()
-- doCreateBinary analysisName = runPerFileAnalysis `runThen` writeCompiledFiles
--   where
--     writeCompiledFiles :: (r, [(Filename, B.ByteString)]) -> IO r
--     writeCompiledFiles (report, bins) = do
--       outputFiles inSrc outSrc bins
--       pure report

-- FIXME
{-
compilePerFile :: (Describe e, Describe e', Describe w, Describe r) =>
                  Text
               -> FileOrDir
               -> FilePath
               -> AnalysisRunner e w IO [ProgramFile] (r, [Either e' ProgramFile]) ()
compilePerFile analysisName inSrc outSrc =
    runPerFileAnalysis `runThen` writeCompiledFiles
  where
    writeCompiledFiles :: (r, [(Filename, B.ByteString)]) -> IO r
    writeCompiledFiles (report, bins) = do
      outputFiles inSrc outSrc bins
      pure report
-}

-- | Given an analysis program for a single file, run it over every input file
-- and collect the reports, then print those reports to standard output.
describePerFileAnalysisP
  :: (MonadIO m, Describe r, ExitCodeOfReport r, Describe w, Describe e, NFData e, NFData w, NFData r)
  => Text -> AnalysisRunnerP e w m ProgramFile r (AnalysisReport e w r)
describePerFileAnalysisP :: Text -> AnalysisRunnerP e w m ProgramFile r (AnalysisReport e w r)
describePerFileAnalysisP Text
analysisName AnalysisProgram e w m ProgramFile r
program LogOutput m
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles = do
  AnalysisRunnerP e w m ProgramFile r (AnalysisReport e w r)
forall (m :: * -> *) e w b.
(MonadIO m, Describe e, Describe w, NFData e, NFData w,
 NFData b) =>
AnalysisRunnerP e w m ProgramFile b (AnalysisReport e w b)
runPerFileAnalysisP AnalysisProgram e w m ProgramFile r
program LogOutput m
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles Pipe (ProgramFile, SourceText) (AnalysisReport e w r) m ()
-> Proxy () (AnalysisReport e w r) () (AnalysisReport e w r) m ()
-> Pipe (ProgramFile, SourceText) (AnalysisReport e w r) m ()
forall (m :: * -> *) a' a b r c' c.
Functor m =>
Proxy a' a () b m r -> Proxy () b c' c m r -> Proxy a' a c' c m r
>->
    ((AnalysisReport e w r -> m (AnalysisReport e w r))
-> Proxy () (AnalysisReport e w r) () (AnalysisReport e w r) m ()
forall (m :: * -> *) a b r. Monad m => (a -> m b) -> Pipe a b m r
P.mapM ((AnalysisReport e w r -> m (AnalysisReport e w r))
 -> Proxy () (AnalysisReport e w r) () (AnalysisReport e w r) m ())
-> (AnalysisReport e w r -> m (AnalysisReport e w r))
-> Proxy () (AnalysisReport e w r) () (AnalysisReport e w r) m ()
forall a b. (a -> b) -> a -> b
$ \ AnalysisReport e w r
r -> IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Text -> Maybe LogLevel -> Bool -> AnalysisReport e w r -> IO ()
forall e w r (m :: * -> *).
(Describe e, Describe w, Describe r, MonadIO m) =>
Text -> Maybe LogLevel -> Bool -> AnalysisReport e w r -> m ()
putDescribeReport Text
analysisName (LogLevel -> Maybe LogLevel
forall a. a -> Maybe a
Just LogLevel
logLevel) Bool
snippets AnalysisReport e w r
r) m () -> m (AnalysisReport e w r) -> m (AnalysisReport e w r)
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> AnalysisReport e w r -> m (AnalysisReport e w r)
forall (f :: * -> *) a. Applicative f => a -> f a
pure AnalysisReport e w r
r)

-- | Accepts an analysis program for multiple input files which produces a
-- result value along with refactored files. Performs the refactoring, and
-- prints the result value with the report.
doRefactor
  :: (Describe e, Describe e', Describe w, Describe r, ExitCodeOfReport r)
  => Text
  -> FileOrDir -> FilePath
  -> AnalysisRunner e w IO [ProgramFile] (r, [Either e' ProgramFile]) Int
doRefactor :: Text
-> FilePath
-> FilePath
-> AnalysisRunner
     e w IO [ProgramFile] (r, [Either e' ProgramFile]) Int
doRefactor Text
analysisName FilePath
inSrc FilePath
outSrc AnalysisProgram e w IO [ProgramFile] (r, [Either e' ProgramFile])
program LogOutput IO
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles [(ProgramFile, SourceText)]
pfsTexts = do
  AnalysisReport e w (r, [Either e' ProgramFile])
report <- AnalysisRunner
  e
  w
  IO
  [ProgramFile]
  (r, [Either e' ProgramFile])
  (AnalysisReport e w (r, [Either e' ProgramFile]))
forall (m :: * -> *) e w b.
(Monad m, Describe e, Describe w) =>
AnalysisRunner e w m [ProgramFile] b (AnalysisReport e w b)
runMultiFileAnalysis AnalysisProgram e w IO [ProgramFile] (r, [Either e' ProgramFile])
program LogOutput IO
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles [(ProgramFile, SourceText)]
pfsTexts

  let
    -- Get the user-facing output from the report
    report' :: AnalysisReport e w r
report' = ((r, [Either e' ProgramFile]) -> r)
-> AnalysisReport e w (r, [Either e' ProgramFile])
-> AnalysisReport e w r
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (r, [Either e' ProgramFile]) -> r
forall a b. (a, b) -> a
fst AnalysisReport e w (r, [Either e' ProgramFile])
report
    -- Get the refactoring result form the report
    resultFiles :: Maybe [Either e' ProgramFile]
resultFiles = AnalysisReport e w (r, [Either e' ProgramFile])
report AnalysisReport e w (r, [Either e' ProgramFile])
-> Getting
     (First [Either e' ProgramFile])
     (AnalysisReport e w (r, [Either e' ProgramFile]))
     [Either e' ProgramFile]
-> Maybe [Either e' ProgramFile]
forall s a. s -> Getting (First a) s a -> Maybe a
^? (AnalysisResult e (r, [Either e' ProgramFile])
 -> Const
      (First [Either e' ProgramFile])
      (AnalysisResult e (r, [Either e' ProgramFile])))
-> AnalysisReport e w (r, [Either e' ProgramFile])
-> Const
     (First [Either e' ProgramFile])
     (AnalysisReport e w (r, [Either e' ProgramFile]))
forall e w r1 r2.
Lens
  (AnalysisReport e w r1)
  (AnalysisReport e w r2)
  (AnalysisResult e r1)
  (AnalysisResult e r2)
arResult ((AnalysisResult e (r, [Either e' ProgramFile])
  -> Const
       (First [Either e' ProgramFile])
       (AnalysisResult e (r, [Either e' ProgramFile])))
 -> AnalysisReport e w (r, [Either e' ProgramFile])
 -> Const
      (First [Either e' ProgramFile])
      (AnalysisReport e w (r, [Either e' ProgramFile])))
-> (([Either e' ProgramFile]
     -> Const (First [Either e' ProgramFile]) [Either e' ProgramFile])
    -> AnalysisResult e (r, [Either e' ProgramFile])
    -> Const
         (First [Either e' ProgramFile])
         (AnalysisResult e (r, [Either e' ProgramFile])))
-> Getting
     (First [Either e' ProgramFile])
     (AnalysisReport e w (r, [Either e' ProgramFile]))
     [Either e' ProgramFile]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((r, [Either e' ProgramFile])
 -> Const
      (First [Either e' ProgramFile]) (r, [Either e' ProgramFile]))
-> AnalysisResult e (r, [Either e' ProgramFile])
-> Const
     (First [Either e' ProgramFile])
     (AnalysisResult e (r, [Either e' ProgramFile]))
forall e r1 r2.
Prism (AnalysisResult e r2) (AnalysisResult e r1) r2 r1
_ARSuccess (((r, [Either e' ProgramFile])
  -> Const
       (First [Either e' ProgramFile]) (r, [Either e' ProgramFile]))
 -> AnalysisResult e (r, [Either e' ProgramFile])
 -> Const
      (First [Either e' ProgramFile])
      (AnalysisResult e (r, [Either e' ProgramFile])))
-> (([Either e' ProgramFile]
     -> Const (First [Either e' ProgramFile]) [Either e' ProgramFile])
    -> (r, [Either e' ProgramFile])
    -> Const
         (First [Either e' ProgramFile]) (r, [Either e' ProgramFile]))
-> ([Either e' ProgramFile]
    -> Const (First [Either e' ProgramFile]) [Either e' ProgramFile])
-> AnalysisResult e (r, [Either e' ProgramFile])
-> Const
     (First [Either e' ProgramFile])
     (AnalysisResult e (r, [Either e' ProgramFile]))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Either e' ProgramFile]
 -> Const (First [Either e' ProgramFile]) [Either e' ProgramFile])
-> (r, [Either e' ProgramFile])
-> Const
     (First [Either e' ProgramFile]) (r, [Either e' ProgramFile])
forall s t a b. Field2 s t a b => Lens s t a b
_2

  Text -> Maybe LogLevel -> Bool -> AnalysisReport e w r -> IO ()
forall e w r (m :: * -> *).
(Describe e, Describe w, Describe r, MonadIO m) =>
Text -> Maybe LogLevel -> Bool -> AnalysisReport e w r -> m ()
putDescribeReport Text
analysisName (LogLevel -> Maybe LogLevel
forall a. a -> Maybe a
Just LogLevel
logLevel) Bool
snippets AnalysisReport e w r
report'

  -- If the refactoring succeeded, change the files
  case Maybe [Either e' ProgramFile]
resultFiles of
    Just [Either e' ProgramFile]
fs -> FilePath
-> FilePath -> [SourceText] -> [Either e' ProgramFile] -> IO ()
forall e.
FilePath
-> FilePath -> [SourceText] -> [Either e ProgramFile] -> IO ()
finishRefactor FilePath
inSrc FilePath
outSrc (((ProgramFile, SourceText) -> SourceText)
-> [(ProgramFile, SourceText)] -> [SourceText]
forall a b. (a -> b) -> [a] -> [b]
map (ProgramFile, SourceText) -> SourceText
forall a b. (a, b) -> b
snd [(ProgramFile, SourceText)]
pfsTexts) [Either e' ProgramFile]
fs IO () -> IO Int -> IO Int
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>>
               Int -> IO Int
forall (m :: * -> *) a. Monad m => a -> m a
return (AnalysisReport e w r -> Int
forall a. ExitCodeOfReport a => a -> Int
exitCodeOf AnalysisReport e w r
report')
    Maybe [Either e' ProgramFile]
Nothing -> Int -> IO Int
forall (m :: * -> *) a. Monad m => a -> m a
return (AnalysisReport e w r -> Int
forall a. ExitCodeOfReport a => a -> Int
exitCodeOf AnalysisReport e w r
report')

-- | Accepts an analysis program for multiple input files which produces
-- refactored files and creates new files. Performs the refactoring.
doRefactorAndCreate
  :: (Describe e, Describe w)
  => Text
  -> FileOrDir -> FilePath
  -> AnalysisRunner e w IO [ProgramFile] ([ProgramFile], [ProgramFile]) Int
doRefactorAndCreate :: Text
-> FilePath
-> FilePath
-> AnalysisRunner
     e w IO [ProgramFile] ([ProgramFile], [ProgramFile]) Int
doRefactorAndCreate Text
analysisName FilePath
inSrc FilePath
outSrc AnalysisProgram e w IO [ProgramFile] ([ProgramFile], [ProgramFile])
program LogOutput IO
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles [(ProgramFile, SourceText)]
pfsTexts = do
  AnalysisReport e w ([ProgramFile], [ProgramFile])
report <- AnalysisRunner
  e
  w
  IO
  [ProgramFile]
  ([ProgramFile], [ProgramFile])
  (AnalysisReport e w ([ProgramFile], [ProgramFile]))
forall (m :: * -> *) e w b.
(Monad m, Describe e, Describe w) =>
AnalysisRunner e w m [ProgramFile] b (AnalysisReport e w b)
runMultiFileAnalysis AnalysisProgram e w IO [ProgramFile] ([ProgramFile], [ProgramFile])
program LogOutput IO
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles [(ProgramFile, SourceText)]
pfsTexts

  let
    -- Get the user-facing output from the report
    report' :: AnalysisReport e w ()
report' = (([ProgramFile], [ProgramFile]) -> ())
-> AnalysisReport e w ([ProgramFile], [ProgramFile])
-> AnalysisReport e w ()
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (() -> ([ProgramFile], [ProgramFile]) -> ()
forall a b. a -> b -> a
const ()) AnalysisReport e w ([ProgramFile], [ProgramFile])
report
    -- Get the refactoring result form the report
    resultFiles :: Maybe ([ProgramFile], [ProgramFile])
resultFiles = AnalysisReport e w ([ProgramFile], [ProgramFile])
report AnalysisReport e w ([ProgramFile], [ProgramFile])
-> Getting
     (First ([ProgramFile], [ProgramFile]))
     (AnalysisReport e w ([ProgramFile], [ProgramFile]))
     ([ProgramFile], [ProgramFile])
-> Maybe ([ProgramFile], [ProgramFile])
forall s a. s -> Getting (First a) s a -> Maybe a
^? (AnalysisResult e ([ProgramFile], [ProgramFile])
 -> Const
      (First ([ProgramFile], [ProgramFile]))
      (AnalysisResult e ([ProgramFile], [ProgramFile])))
-> AnalysisReport e w ([ProgramFile], [ProgramFile])
-> Const
     (First ([ProgramFile], [ProgramFile]))
     (AnalysisReport e w ([ProgramFile], [ProgramFile]))
forall e w r1 r2.
Lens
  (AnalysisReport e w r1)
  (AnalysisReport e w r2)
  (AnalysisResult e r1)
  (AnalysisResult e r2)
arResult ((AnalysisResult e ([ProgramFile], [ProgramFile])
  -> Const
       (First ([ProgramFile], [ProgramFile]))
       (AnalysisResult e ([ProgramFile], [ProgramFile])))
 -> AnalysisReport e w ([ProgramFile], [ProgramFile])
 -> Const
      (First ([ProgramFile], [ProgramFile]))
      (AnalysisReport e w ([ProgramFile], [ProgramFile])))
-> ((([ProgramFile], [ProgramFile])
     -> Const
          (First ([ProgramFile], [ProgramFile]))
          ([ProgramFile], [ProgramFile]))
    -> AnalysisResult e ([ProgramFile], [ProgramFile])
    -> Const
         (First ([ProgramFile], [ProgramFile]))
         (AnalysisResult e ([ProgramFile], [ProgramFile])))
-> Getting
     (First ([ProgramFile], [ProgramFile]))
     (AnalysisReport e w ([ProgramFile], [ProgramFile]))
     ([ProgramFile], [ProgramFile])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (([ProgramFile], [ProgramFile])
 -> Const
      (First ([ProgramFile], [ProgramFile]))
      ([ProgramFile], [ProgramFile]))
-> AnalysisResult e ([ProgramFile], [ProgramFile])
-> Const
     (First ([ProgramFile], [ProgramFile]))
     (AnalysisResult e ([ProgramFile], [ProgramFile]))
forall e r1 r2.
Prism (AnalysisResult e r2) (AnalysisResult e r1) r2 r1
_ARSuccess

  Text -> Maybe LogLevel -> Bool -> AnalysisReport e w () -> IO ()
forall e w r (m :: * -> *).
(Describe e, Describe w, Describe r, MonadIO m) =>
Text -> Maybe LogLevel -> Bool -> AnalysisReport e w r -> m ()
putDescribeReport Text
analysisName (LogLevel -> Maybe LogLevel
forall a. a -> Maybe a
Just LogLevel
logLevel) Bool
snippets AnalysisReport e w ()
report'

  case Maybe ([ProgramFile], [ProgramFile])
resultFiles of
    -- If the refactoring succeeded, change the files
    Just ([ProgramFile], [ProgramFile])
fs -> FilePath
-> FilePath
-> [SourceText]
-> ([ProgramFile], [ProgramFile])
-> IO ()
finishRefactorAndCreate FilePath
inSrc FilePath
outSrc (((ProgramFile, SourceText) -> SourceText)
-> [(ProgramFile, SourceText)] -> [SourceText]
forall a b. (a -> b) -> [a] -> [b]
map (ProgramFile, SourceText) -> SourceText
forall a b. (a, b) -> b
snd [(ProgramFile, SourceText)]
pfsTexts) ([ProgramFile], [ProgramFile])
fs IO () -> IO Int -> IO Int
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>>
               Int -> IO Int
forall (m :: * -> *) a. Monad m => a -> m a
return (AnalysisReport e w () -> Int
forall a. ExitCodeOfReport a => a -> Int
exitCodeOf AnalysisReport e w ()
report')
    Maybe ([ProgramFile], [ProgramFile])
Nothing -> Int -> IO Int
forall (m :: * -> *) a. Monad m => a -> m a
return (AnalysisReport e w () -> Int
forall a. ExitCodeOfReport a => a -> Int
exitCodeOf AnalysisReport e w ()
report')

-- | Accepts an analysis program to refactor a single file and returns an
-- analysis program to refactor each input file with that refactoring.
perFileRefactoring
  :: (Monad m)
  => AnalysisProgram e w m ProgramFile ProgramFile
  -> AnalysisProgram e w m [ProgramFile] ((), [Either e ProgramFile])
perFileRefactoring :: AnalysisProgram e w m ProgramFile ProgramFile
-> AnalysisProgram e w m [ProgramFile] ((), [Either e ProgramFile])
perFileRefactoring AnalysisProgram e w m ProgramFile ProgramFile
program [ProgramFile]
pfs = do
  [ProgramFile]
pfs' <- AnalysisProgram e w m ProgramFile ProgramFile
-> [ProgramFile] -> AnalysisT e w m [ProgramFile]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM AnalysisProgram e w m ProgramFile ProgramFile
program [ProgramFile]
pfs
  ((), [Either e ProgramFile])
-> AnalysisT e w m ((), [Either e ProgramFile])
forall (m :: * -> *) a. Monad m => a -> m a
return ((), (ProgramFile -> Either e ProgramFile)
-> [ProgramFile] -> [Either e ProgramFile]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ProgramFile -> Either e ProgramFile
forall (f :: * -> *) a. Applicative f => a -> f a
pure [ProgramFile]
pfs')

--------------------------------------------------------------------------------
--  Refactoring Combinators
--------------------------------------------------------------------------------

finishRefactor
  :: FileOrDir -> FilePath
  -> [SourceText]
  -- ^ Original source from the input files
  -> [Either e ProgramFile]
  -- ^ Changed input files (or errors)
  -> IO ()
finishRefactor :: FilePath
-> FilePath -> [SourceText] -> [Either e ProgramFile] -> IO ()
finishRefactor FilePath
inSrc FilePath
outSrc [SourceText]
inputText [Either e ProgramFile]
analysisOutput = do
  let ([e]
_, [ProgramFile]
ps') = [Either e ProgramFile] -> ([e], [ProgramFile])
forall a b. [Either a b] -> ([a], [b])
partitionEithers [Either e ProgramFile]
analysisOutput
      outputs :: [(ProgramFile, SourceText)]
outputs = [SourceText] -> [ProgramFile] -> [(ProgramFile, SourceText)]
reassociateSourceText [SourceText]
inputText [ProgramFile]
ps'

  FilePath -> FilePath -> [(ProgramFile, SourceText)] -> IO ()
forall t. OutputFiles t => FilePath -> FilePath -> [t] -> IO ()
outputFiles FilePath
inSrc FilePath
outSrc [(ProgramFile, SourceText)]
outputs


finishRefactorAndCreate
  :: FileOrDir -> FilePath
  -> [SourceText]
  -- ^ Original source from the input files
  -> ([ProgramFile], [ProgramFile])
  -- ^ Changed input files, newly created files
  -> IO ()
finishRefactorAndCreate :: FilePath
-> FilePath
-> [SourceText]
-> ([ProgramFile], [ProgramFile])
-> IO ()
finishRefactorAndCreate FilePath
inSrc FilePath
outSrc [SourceText]
inputText ([ProgramFile], [ProgramFile])
analysisOutput = do

  let changedFiles :: [(ProgramFile, SourceText)]
changedFiles = [SourceText] -> [ProgramFile] -> [(ProgramFile, SourceText)]
reassociateSourceText [SourceText]
inputText (([ProgramFile], [ProgramFile]) -> [ProgramFile]
forall a b. (a, b) -> a
fst ([ProgramFile], [ProgramFile])
analysisOutput)
      newFiles :: [(ProgramFile, SourceText)]
newFiles = (ProgramFile -> (ProgramFile, SourceText))
-> [ProgramFile] -> [(ProgramFile, SourceText)]
forall a b. (a -> b) -> [a] -> [b]
map (\ProgramFile
pf -> (ProgramFile
pf, SourceText
B.empty)) (([ProgramFile], [ProgramFile]) -> [ProgramFile]
forall a b. (a, b) -> b
snd ([ProgramFile], [ProgramFile])
analysisOutput)

  FilePath -> FilePath -> [(ProgramFile, SourceText)] -> IO ()
forall t. OutputFiles t => FilePath -> FilePath -> [t] -> IO ()
outputFiles FilePath
inSrc FilePath
outSrc [(ProgramFile, SourceText)]
changedFiles
  FilePath -> FilePath -> [(ProgramFile, SourceText)] -> IO ()
forall t. OutputFiles t => FilePath -> FilePath -> [t] -> IO ()
outputFiles FilePath
inSrc FilePath
outSrc [(ProgramFile, SourceText)]
newFiles

--------------------------------------------------------------------------------
--  Combinators
--------------------------------------------------------------------------------

-- | Monadic bind for analysis runners.
runThen
  :: (Monad m)
  => AnalysisRunner e w m a b r -> (r -> m r')
  -> AnalysisRunner e w m a b r'
runThen :: AnalysisRunner e w m a b r
-> (r -> m r') -> AnalysisRunner e w m a b r'
runThen AnalysisRunner e w m a b r
runner r -> m r'
withResult AnalysisProgram e w m a b
program LogOutput m
output LogLevel
level Bool
snippets ModFiles
modFiles [(ProgramFile, SourceText)]
programFiles =
  AnalysisRunner e w m a b r
runner AnalysisProgram e w m a b
program LogOutput m
output LogLevel
level Bool
snippets ModFiles
modFiles [(ProgramFile, SourceText)]
programFiles m r -> (r -> m r') -> m r'
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= r -> m r'
withResult

--------------------------------------------------------------------------------
--  Misc
--------------------------------------------------------------------------------

-- | Class for default values of some type 't'
class Default t where
    defaultValue :: t

-- | Print a string to the user informing them of files excluded
-- from the operation.
printExcludes :: Filename -> [Filename] -> IO ()
printExcludes :: FilePath -> [FilePath] -> IO ()
printExcludes FilePath
_ []           = () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
printExcludes FilePath
_ [FilePath
""]         = () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
printExcludes FilePath
inSrc [FilePath]
excludes =
  FilePath -> IO ()
putStrLn (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ [FilePath] -> FilePath
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [FilePath
"Excluding ", FilePath -> [FilePath] -> FilePath
forall a. [a] -> [[a]] -> [a]
intercalate FilePath
"," [FilePath]
excludes, FilePath
" from ", FilePath
inSrc, FilePath
"/"]


-- | For refactorings which create additional files.
type ProgramFile = F.ProgramFile A

reassociateSourceText
  :: [SourceText]
  -> [F.ProgramFile Annotation]
  -> [(F.ProgramFile Annotation, SourceText)]
reassociateSourceText :: [SourceText] -> [ProgramFile] -> [(ProgramFile, SourceText)]
reassociateSourceText [SourceText]
ps [ProgramFile]
ps' = [ProgramFile] -> [SourceText] -> [(ProgramFile, SourceText)]
forall a b. [a] -> [b] -> [(a, b)]
zip [ProgramFile]
ps' [SourceText]
ps


loadModAndProgramFiles
  :: (MonadIO m)
  => Maybe FortranVersion
  -> MFCompiler r m -> r
  -> FileOrDir -- ^ Input source file or directory
  -> FileOrDir -- ^ Include path
  -> [Filename] -- ^ Excluded files
  -> m (ModFiles, [(ProgramFile, SourceText)])
loadModAndProgramFiles :: Maybe FortranVersion
-> MFCompiler r m
-> r
-> FilePath
-> FilePath
-> [FilePath]
-> m (ModFiles, [(ProgramFile, SourceText)])
loadModAndProgramFiles Maybe FortranVersion
mv MFCompiler r m
mfc r
env FilePath
inSrc FilePath
incDir [FilePath]
excludes = do
  IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ FilePath -> [FilePath] -> IO ()
printExcludes FilePath
inSrc [FilePath]
excludes
  ModFiles
modFiles <- Maybe FortranVersion
-> ModFiles
-> MFCompiler r m
-> r
-> FilePath
-> [FilePath]
-> m ModFiles
forall (m :: * -> *) r.
MonadIO m =>
Maybe FortranVersion
-> ModFiles
-> MFCompiler r m
-> r
-> FilePath
-> [FilePath]
-> m ModFiles
genModFiles Maybe FortranVersion
mv ModFiles
emptyModFiles MFCompiler r m
mfc r
env FilePath
incDir [FilePath]
excludes
  [(ProgramFile, SourceText)]
ps <- IO [(ProgramFile, SourceText)] -> m [(ProgramFile, SourceText)]
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [(ProgramFile, SourceText)] -> m [(ProgramFile, SourceText)])
-> IO [(ProgramFile, SourceText)] -> m [(ProgramFile, SourceText)]
forall a b. (a -> b) -> a -> b
$ Maybe FortranVersion
-> ModFiles
-> FilePath
-> [FilePath]
-> IO [(ProgramFile, SourceText)]
readParseSrcDir Maybe FortranVersion
mv ModFiles
modFiles FilePath
inSrc [FilePath]
excludes
  (ModFiles, [(ProgramFile, SourceText)])
-> m (ModFiles, [(ProgramFile, SourceText)])
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ModFiles
modFiles, [(ProgramFile, SourceText)]
ps)