{- |
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.Version      (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 :: forall (m :: * -> *) e w b.
(MonadIO m, Describe e, Describe w, NFData e, NFData w,
 NFData b) =>
AnalysisRunnerP
  e w m (ProgramFile Annotation) b (AnalysisReport e w b)
runPerFileAnalysisP AnalysisProgram e w m (ProgramFile Annotation) b
program LogOutput m
logOutput LogLevel
logLevel Bool
_ ModFiles
modFiles =
  ((ProgramFile Annotation, SourceText) -> m (AnalysisReport e w b))
-> Pipe
     (ProgramFile Annotation, SourceText) (AnalysisReport e w b) m ()
forall (m :: * -> *) a b r. Monad m => (a -> m b) -> Pipe a b m r
P.mapM (((ProgramFile Annotation, SourceText) -> m (AnalysisReport e w b))
 -> Pipe
      (ProgramFile Annotation, SourceText) (AnalysisReport e w b) m ())
-> ((ProgramFile Annotation, SourceText)
    -> m (AnalysisReport e w b))
-> Pipe
     (ProgramFile Annotation, SourceText) (AnalysisReport e w b) m ()
forall a b. (a -> b) -> a -> b
$ \ (ProgramFile Annotation
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 Annotation -> FilePath
forall a. ProgramFile a -> FilePath
F.pfGetFilename ProgramFile Annotation
pf)
                 LogOutput m
logOutput
                 LogLevel
logLevel
                 ModFiles
modFiles
                 (AnalysisProgram e w m (ProgramFile Annotation) b
program ProgramFile Annotation
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 :: forall (m :: * -> *) e w b.
(Monad m, Describe e, Describe w) =>
AnalysisRunner
  e w m [ProgramFile Annotation] b (AnalysisReport e w b)
runMultiFileAnalysis AnalysisProgram e w m [ProgramFile Annotation] 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 Annotation, SourceText)] -> AnalysisT e w m b)
-> [(ProgramFile Annotation, SourceText)]
-> m (AnalysisReport e w b)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AnalysisProgram e w m [ProgramFile Annotation] b
program AnalysisProgram e w m [ProgramFile Annotation] b
-> ([(ProgramFile Annotation, SourceText)]
    -> [ProgramFile Annotation])
-> [(ProgramFile Annotation, SourceText)]
-> AnalysisT e w m b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((ProgramFile Annotation, SourceText) -> ProgramFile Annotation)
-> [(ProgramFile Annotation, SourceText)]
-> [ProgramFile Annotation]
forall a b. (a -> b) -> [a] -> [b]
map (ProgramFile Annotation, SourceText) -> ProgramFile Annotation
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 :: forall (m :: * -> *) r w e.
(MonadIO m, Describe r, ExitCodeOfReport r, Describe w, Describe e,
 NFData e, NFData w, NFData r) =>
Text
-> AnalysisRunnerP
     e w m (ProgramFile Annotation) r (AnalysisReport e w r)
describePerFileAnalysisP Text
analysisName AnalysisProgram e w m (ProgramFile Annotation) r
program LogOutput m
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles = do
  AnalysisRunnerP
  e w m (ProgramFile Annotation) 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 Annotation) b (AnalysisReport e w b)
runPerFileAnalysisP AnalysisProgram e w m (ProgramFile Annotation) r
program LogOutput m
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles Pipe
  (ProgramFile Annotation, SourceText) (AnalysisReport e w r) m ()
-> Proxy () (AnalysisReport e w r) () (AnalysisReport e w r) m ()
-> Pipe
     (ProgramFile Annotation, 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 a. IO a -> m a
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 a b. m a -> m b -> m b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> AnalysisReport e w r -> m (AnalysisReport e w r)
forall a. a -> m a
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 :: forall e e' w r.
(Describe e, Describe e', Describe w, Describe r,
 ExitCodeOfReport r) =>
Text
-> FilePath
-> FilePath
-> AnalysisRunner
     e
     w
     IO
     [ProgramFile Annotation]
     (r, [Either e' (ProgramFile Annotation)])
     Int
doRefactor Text
analysisName FilePath
inSrc FilePath
outSrc AnalysisProgram
  e
  w
  IO
  [ProgramFile Annotation]
  (r, [Either e' (ProgramFile Annotation)])
program LogOutput IO
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles [(ProgramFile Annotation, SourceText)]
pfsTexts = do
  AnalysisReport e w (r, [Either e' (ProgramFile Annotation)])
report <- AnalysisRunner
  e
  w
  IO
  [ProgramFile Annotation]
  (r, [Either e' (ProgramFile Annotation)])
  (AnalysisReport e w (r, [Either e' (ProgramFile Annotation)]))
forall (m :: * -> *) e w b.
(Monad m, Describe e, Describe w) =>
AnalysisRunner
  e w m [ProgramFile Annotation] b (AnalysisReport e w b)
runMultiFileAnalysis AnalysisProgram
  e
  w
  IO
  [ProgramFile Annotation]
  (r, [Either e' (ProgramFile Annotation)])
program LogOutput IO
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles [(ProgramFile Annotation, SourceText)]
pfsTexts

  let
    -- Get the user-facing output from the report
    report' :: AnalysisReport e w r
report' = ((r, [Either e' (ProgramFile Annotation)]) -> r)
-> AnalysisReport e w (r, [Either e' (ProgramFile Annotation)])
-> AnalysisReport e w r
forall a b.
(a -> b) -> AnalysisReport e w a -> AnalysisReport e w b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (r, [Either e' (ProgramFile Annotation)]) -> r
forall a b. (a, b) -> a
fst AnalysisReport e w (r, [Either e' (ProgramFile Annotation)])
report
    -- Get the refactoring result form the report
    resultFiles :: Maybe [Either e' (ProgramFile Annotation)]
resultFiles = AnalysisReport e w (r, [Either e' (ProgramFile Annotation)])
report AnalysisReport e w (r, [Either e' (ProgramFile Annotation)])
-> Getting
     (First [Either e' (ProgramFile Annotation)])
     (AnalysisReport e w (r, [Either e' (ProgramFile Annotation)]))
     [Either e' (ProgramFile Annotation)]
-> Maybe [Either e' (ProgramFile Annotation)]
forall s a. s -> Getting (First a) s a -> Maybe a
^? (AnalysisResult e (r, [Either e' (ProgramFile Annotation)])
 -> Const
      (First [Either e' (ProgramFile Annotation)])
      (AnalysisResult e (r, [Either e' (ProgramFile Annotation)])))
-> AnalysisReport e w (r, [Either e' (ProgramFile Annotation)])
-> Const
     (First [Either e' (ProgramFile Annotation)])
     (AnalysisReport e w (r, [Either e' (ProgramFile Annotation)]))
forall e w r1 r2 (f :: * -> *).
Functor f =>
(AnalysisResult e r1 -> f (AnalysisResult e r2))
-> AnalysisReport e w r1 -> f (AnalysisReport e w r2)
arResult ((AnalysisResult e (r, [Either e' (ProgramFile Annotation)])
  -> Const
       (First [Either e' (ProgramFile Annotation)])
       (AnalysisResult e (r, [Either e' (ProgramFile Annotation)])))
 -> AnalysisReport e w (r, [Either e' (ProgramFile Annotation)])
 -> Const
      (First [Either e' (ProgramFile Annotation)])
      (AnalysisReport e w (r, [Either e' (ProgramFile Annotation)])))
-> (([Either e' (ProgramFile Annotation)]
     -> Const
          (First [Either e' (ProgramFile Annotation)])
          [Either e' (ProgramFile Annotation)])
    -> AnalysisResult e (r, [Either e' (ProgramFile Annotation)])
    -> Const
         (First [Either e' (ProgramFile Annotation)])
         (AnalysisResult e (r, [Either e' (ProgramFile Annotation)])))
-> Getting
     (First [Either e' (ProgramFile Annotation)])
     (AnalysisReport e w (r, [Either e' (ProgramFile Annotation)]))
     [Either e' (ProgramFile Annotation)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((r, [Either e' (ProgramFile Annotation)])
 -> Const
      (First [Either e' (ProgramFile Annotation)])
      (r, [Either e' (ProgramFile Annotation)]))
-> AnalysisResult e (r, [Either e' (ProgramFile Annotation)])
-> Const
     (First [Either e' (ProgramFile Annotation)])
     (AnalysisResult e (r, [Either e' (ProgramFile Annotation)]))
forall e r1 r2 (p :: * -> * -> *) (f :: * -> *).
(Choice p, Applicative f) =>
p r1 (f r2) -> p (AnalysisResult e r1) (f (AnalysisResult e r2))
_ARSuccess (((r, [Either e' (ProgramFile Annotation)])
  -> Const
       (First [Either e' (ProgramFile Annotation)])
       (r, [Either e' (ProgramFile Annotation)]))
 -> AnalysisResult e (r, [Either e' (ProgramFile Annotation)])
 -> Const
      (First [Either e' (ProgramFile Annotation)])
      (AnalysisResult e (r, [Either e' (ProgramFile Annotation)])))
-> (([Either e' (ProgramFile Annotation)]
     -> Const
          (First [Either e' (ProgramFile Annotation)])
          [Either e' (ProgramFile Annotation)])
    -> (r, [Either e' (ProgramFile Annotation)])
    -> Const
         (First [Either e' (ProgramFile Annotation)])
         (r, [Either e' (ProgramFile Annotation)]))
-> ([Either e' (ProgramFile Annotation)]
    -> Const
         (First [Either e' (ProgramFile Annotation)])
         [Either e' (ProgramFile Annotation)])
-> AnalysisResult e (r, [Either e' (ProgramFile Annotation)])
-> Const
     (First [Either e' (ProgramFile Annotation)])
     (AnalysisResult e (r, [Either e' (ProgramFile Annotation)]))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Either e' (ProgramFile Annotation)]
 -> Const
      (First [Either e' (ProgramFile Annotation)])
      [Either e' (ProgramFile Annotation)])
-> (r, [Either e' (ProgramFile Annotation)])
-> Const
     (First [Either e' (ProgramFile Annotation)])
     (r, [Either e' (ProgramFile Annotation)])
forall s t a b. Field2 s t a b => Lens s t a b
Lens
  (r, [Either e' (ProgramFile Annotation)])
  (r, [Either e' (ProgramFile Annotation)])
  [Either e' (ProgramFile Annotation)]
  [Either e' (ProgramFile Annotation)]
_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 Annotation)]
resultFiles of
    Just [Either e' (ProgramFile Annotation)]
fs -> FilePath
-> FilePath
-> [SourceText]
-> [Either e' (ProgramFile Annotation)]
-> IO ()
forall e.
FilePath
-> FilePath
-> [SourceText]
-> [Either e (ProgramFile Annotation)]
-> IO ()
finishRefactor FilePath
inSrc FilePath
outSrc (((ProgramFile Annotation, SourceText) -> SourceText)
-> [(ProgramFile Annotation, SourceText)] -> [SourceText]
forall a b. (a -> b) -> [a] -> [b]
map (ProgramFile Annotation, SourceText) -> SourceText
forall a b. (a, b) -> b
snd [(ProgramFile Annotation, SourceText)]
pfsTexts) [Either e' (ProgramFile Annotation)]
fs IO () -> IO Int -> IO Int
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>>
               Int -> IO Int
forall a. a -> IO a
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 Annotation)]
Nothing -> Int -> IO Int
forall a. a -> IO a
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 :: forall e w.
(Describe e, Describe w) =>
Text
-> FilePath
-> FilePath
-> AnalysisRunner
     e
     w
     IO
     [ProgramFile Annotation]
     ([ProgramFile Annotation], [ProgramFile Annotation])
     Int
doRefactorAndCreate Text
analysisName FilePath
inSrc FilePath
outSrc AnalysisProgram
  e
  w
  IO
  [ProgramFile Annotation]
  ([ProgramFile Annotation], [ProgramFile Annotation])
program LogOutput IO
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles [(ProgramFile Annotation, SourceText)]
pfsTexts = do
  AnalysisReport
  e w ([ProgramFile Annotation], [ProgramFile Annotation])
report <- AnalysisRunner
  e
  w
  IO
  [ProgramFile Annotation]
  ([ProgramFile Annotation], [ProgramFile Annotation])
  (AnalysisReport
     e w ([ProgramFile Annotation], [ProgramFile Annotation]))
forall (m :: * -> *) e w b.
(Monad m, Describe e, Describe w) =>
AnalysisRunner
  e w m [ProgramFile Annotation] b (AnalysisReport e w b)
runMultiFileAnalysis AnalysisProgram
  e
  w
  IO
  [ProgramFile Annotation]
  ([ProgramFile Annotation], [ProgramFile Annotation])
program LogOutput IO
logOutput LogLevel
logLevel Bool
snippets ModFiles
modFiles [(ProgramFile Annotation, SourceText)]
pfsTexts

  let
    -- Get the user-facing output from the report
    report' :: AnalysisReport e w ()
report' = (([ProgramFile Annotation], [ProgramFile Annotation]) -> ())
-> AnalysisReport
     e w ([ProgramFile Annotation], [ProgramFile Annotation])
-> AnalysisReport e w ()
forall a b.
(a -> b) -> AnalysisReport e w a -> AnalysisReport e w b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (() -> ([ProgramFile Annotation], [ProgramFile Annotation]) -> ()
forall a b. a -> b -> a
const ()) AnalysisReport
  e w ([ProgramFile Annotation], [ProgramFile Annotation])
report
    -- Get the refactoring result form the report
    resultFiles :: Maybe ([ProgramFile Annotation], [ProgramFile Annotation])
resultFiles = AnalysisReport
  e w ([ProgramFile Annotation], [ProgramFile Annotation])
report AnalysisReport
  e w ([ProgramFile Annotation], [ProgramFile Annotation])
-> Getting
     (First ([ProgramFile Annotation], [ProgramFile Annotation]))
     (AnalysisReport
        e w ([ProgramFile Annotation], [ProgramFile Annotation]))
     ([ProgramFile Annotation], [ProgramFile Annotation])
-> Maybe ([ProgramFile Annotation], [ProgramFile Annotation])
forall s a. s -> Getting (First a) s a -> Maybe a
^? (AnalysisResult
   e ([ProgramFile Annotation], [ProgramFile Annotation])
 -> Const
      (First ([ProgramFile Annotation], [ProgramFile Annotation]))
      (AnalysisResult
         e ([ProgramFile Annotation], [ProgramFile Annotation])))
-> AnalysisReport
     e w ([ProgramFile Annotation], [ProgramFile Annotation])
-> Const
     (First ([ProgramFile Annotation], [ProgramFile Annotation]))
     (AnalysisReport
        e w ([ProgramFile Annotation], [ProgramFile Annotation]))
forall e w r1 r2 (f :: * -> *).
Functor f =>
(AnalysisResult e r1 -> f (AnalysisResult e r2))
-> AnalysisReport e w r1 -> f (AnalysisReport e w r2)
arResult ((AnalysisResult
    e ([ProgramFile Annotation], [ProgramFile Annotation])
  -> Const
       (First ([ProgramFile Annotation], [ProgramFile Annotation]))
       (AnalysisResult
          e ([ProgramFile Annotation], [ProgramFile Annotation])))
 -> AnalysisReport
      e w ([ProgramFile Annotation], [ProgramFile Annotation])
 -> Const
      (First ([ProgramFile Annotation], [ProgramFile Annotation]))
      (AnalysisReport
         e w ([ProgramFile Annotation], [ProgramFile Annotation])))
-> ((([ProgramFile Annotation], [ProgramFile Annotation])
     -> Const
          (First ([ProgramFile Annotation], [ProgramFile Annotation]))
          ([ProgramFile Annotation], [ProgramFile Annotation]))
    -> AnalysisResult
         e ([ProgramFile Annotation], [ProgramFile Annotation])
    -> Const
         (First ([ProgramFile Annotation], [ProgramFile Annotation]))
         (AnalysisResult
            e ([ProgramFile Annotation], [ProgramFile Annotation])))
-> Getting
     (First ([ProgramFile Annotation], [ProgramFile Annotation]))
     (AnalysisReport
        e w ([ProgramFile Annotation], [ProgramFile Annotation]))
     ([ProgramFile Annotation], [ProgramFile Annotation])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (([ProgramFile Annotation], [ProgramFile Annotation])
 -> Const
      (First ([ProgramFile Annotation], [ProgramFile Annotation]))
      ([ProgramFile Annotation], [ProgramFile Annotation]))
-> AnalysisResult
     e ([ProgramFile Annotation], [ProgramFile Annotation])
-> Const
     (First ([ProgramFile Annotation], [ProgramFile Annotation]))
     (AnalysisResult
        e ([ProgramFile Annotation], [ProgramFile Annotation]))
forall e r1 r2 (p :: * -> * -> *) (f :: * -> *).
(Choice p, Applicative f) =>
p r1 (f r2) -> p (AnalysisResult e r1) (f (AnalysisResult e r2))
_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 Annotation], [ProgramFile Annotation])
resultFiles of
    -- If the refactoring succeeded, change the files
    Just ([ProgramFile Annotation], [ProgramFile Annotation])
fs -> FilePath
-> FilePath
-> [SourceText]
-> ([ProgramFile Annotation], [ProgramFile Annotation])
-> IO ()
finishRefactorAndCreate FilePath
inSrc FilePath
outSrc (((ProgramFile Annotation, SourceText) -> SourceText)
-> [(ProgramFile Annotation, SourceText)] -> [SourceText]
forall a b. (a -> b) -> [a] -> [b]
map (ProgramFile Annotation, SourceText) -> SourceText
forall a b. (a, b) -> b
snd [(ProgramFile Annotation, SourceText)]
pfsTexts) ([ProgramFile Annotation], [ProgramFile Annotation])
fs IO () -> IO Int -> IO Int
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>>
               Int -> IO Int
forall a. a -> IO a
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 Annotation], [ProgramFile Annotation])
Nothing -> Int -> IO Int
forall a. a -> IO a
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 :: forall (m :: * -> *) e w.
Monad m =>
AnalysisProgram
  e w m (ProgramFile Annotation) (ProgramFile Annotation)
-> AnalysisProgram
     e
     w
     m
     [ProgramFile Annotation]
     ((), [Either e (ProgramFile Annotation)])
perFileRefactoring AnalysisProgram
  e w m (ProgramFile Annotation) (ProgramFile Annotation)
program [ProgramFile Annotation]
pfs = do
  [ProgramFile Annotation]
pfs' <- AnalysisProgram
  e w m (ProgramFile Annotation) (ProgramFile Annotation)
-> [ProgramFile Annotation]
-> AnalysisT e w m [ProgramFile Annotation]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM AnalysisProgram
  e w m (ProgramFile Annotation) (ProgramFile Annotation)
program [ProgramFile Annotation]
pfs
  ((), [Either e (ProgramFile Annotation)])
-> AnalysisT e w m ((), [Either e (ProgramFile Annotation)])
forall a. a -> AnalysisT e w m a
forall (m :: * -> *) a. Monad m => a -> m a
return ((), (ProgramFile Annotation -> Either e (ProgramFile Annotation))
-> [ProgramFile Annotation] -> [Either e (ProgramFile Annotation)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ProgramFile Annotation -> Either e (ProgramFile Annotation)
forall a. a -> Either e a
forall (f :: * -> *) a. Applicative f => a -> f a
pure [ProgramFile Annotation]
pfs')

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

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

  FilePath
-> FilePath -> [(ProgramFile Annotation, SourceText)] -> IO ()
forall t. OutputFiles t => FilePath -> FilePath -> [t] -> IO ()
outputFiles FilePath
inSrc FilePath
outSrc [(ProgramFile Annotation, 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 Annotation], [ProgramFile Annotation])
-> IO ()
finishRefactorAndCreate FilePath
inSrc FilePath
outSrc [SourceText]
inputText ([ProgramFile Annotation], [ProgramFile Annotation])
analysisOutput = do

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

  FilePath
-> FilePath -> [(ProgramFile Annotation, SourceText)] -> IO ()
forall t. OutputFiles t => FilePath -> FilePath -> [t] -> IO ()
outputFiles FilePath
inSrc FilePath
outSrc [(ProgramFile Annotation, SourceText)]
changedFiles
  FilePath
-> FilePath -> [(ProgramFile Annotation, SourceText)] -> IO ()
forall t. OutputFiles t => FilePath -> FilePath -> [t] -> IO ()
outputFiles FilePath
inSrc FilePath
outSrc [(ProgramFile Annotation, 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 :: forall (m :: * -> *) e w a b r r'.
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
runner r -> m r'
withResult AnalysisProgram e w m a b
program LogOutput m
output LogLevel
level Bool
snippets ModFiles
modFiles [(ProgramFile Annotation, 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 Annotation, SourceText)]
programFiles m r -> (r -> m r') -> m r'
forall a b. m a -> (a -> m b) -> m b
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 a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
printExcludes FilePath
_ [FilePath
""]         = () -> IO ()
forall a. a -> IO a
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 Annotation]
-> [(ProgramFile Annotation, SourceText)]
reassociateSourceText [SourceText]
ps [ProgramFile Annotation]
ps' = [ProgramFile Annotation]
-> [SourceText] -> [(ProgramFile Annotation, SourceText)]
forall a b. [a] -> [b] -> [(a, b)]
zip [ProgramFile Annotation]
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 :: forall (m :: * -> *) r.
MonadIO m =>
Maybe FortranVersion
-> MFCompiler r m
-> r
-> FilePath
-> FilePath
-> [FilePath]
-> m (ModFiles, [(ProgramFile Annotation, SourceText)])
loadModAndProgramFiles Maybe FortranVersion
mv MFCompiler r m
mfc r
env FilePath
inSrc FilePath
incDir [FilePath]
excludes = do
  IO () -> m ()
forall a. IO a -> m a
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 Annotation, SourceText)]
ps <- IO [(ProgramFile Annotation, SourceText)]
-> m [(ProgramFile Annotation, SourceText)]
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [(ProgramFile Annotation, SourceText)]
 -> m [(ProgramFile Annotation, SourceText)])
-> IO [(ProgramFile Annotation, SourceText)]
-> m [(ProgramFile Annotation, SourceText)]
forall a b. (a -> b) -> a -> b
$ Maybe FortranVersion
-> ModFiles
-> FilePath
-> [FilePath]
-> IO [(ProgramFile Annotation, SourceText)]
readParseSrcDir Maybe FortranVersion
mv ModFiles
modFiles FilePath
inSrc [FilePath]
excludes
  (ModFiles, [(ProgramFile Annotation, SourceText)])
-> m (ModFiles, [(ProgramFile Annotation, SourceText)])
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ModFiles
modFiles, [(ProgramFile Annotation, SourceText)]
ps)