{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
module Text.Edifact.Scaffolder.Commons.Language
( -- *
saveHaskellModule
, includeSpecification
-- *
, scaffoldModule
-- *
, getRootModuleName
, getRootModuleNameFor
-- *
, scaffoldElements
, ElementWithDefinition
-- *
, parentModule
-- *
, haddockDependencies
-- *
, reexportDependencies
-- *
, importDependencies
, importCombinators
, importNotYetImplementedHelper
-- *
, moduleDeclaration
, Export(..)
-- *
, reexportAlias
, singleImport
, ImportGroup(..)
, Import(..)
, ImportName(..)
, ModuleAlias(..)
, LanguageExtension(..)
) where
import Text.Edifact.Scaffolder.Commons.Formatters (fModuleName,
parens,
simpleQuoted)
import Text.Edifact.Scaffolder.Commons.Logging (say)
import Text.Edifact.Scaffolder.Commons.Text (commaSeparated,
extensions,
formatSpecification,
indent, newline)
import Text.Edifact.Scaffolder.Commons.Types (LanguageExtension (..),
ModuleName (..),
Scaffolding,
getRevision,
hostModule,
revision,
targetDirectory,
(<.>))
import Control.Monad ((>=>))
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (asks)
import Data.Bifunctor (bimap)
import Data.Foldable (traverse_)
import Data.List (intercalate,
uncons)
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import qualified Data.List.NonEmpty as NE (cons, toList)
import Data.List.Split (splitOn)
import Data.String (IsString (..))
import Data.Text as T (Text, drop,
lines,
unlines)
import qualified Data.Text.IO as TIO (readFile,
writeFile)
import Data.Tuple (swap)
import Formatting as F (Format,
bprint, later,
mapf, sformat,
stext, (%))
import System.Directory (createDirectoryIfMissing)
import System.FilePath ((</>))
import System.Process (callCommand)
getRootModuleName :: Scaffolding ModuleName
getRootModuleName =
let prefix host rev = host <.> ModuleName (getRevision rev)
in asks (prefix . hostModule) <*> asks revision
getRootModuleNameFor :: ModuleName -> Scaffolding ModuleName
getRootModuleNameFor name =
let suffix root = root <.> name
in suffix <$> getRootModuleName
saveHaskellModule :: ModuleName -> [Text] -> Scaffolding ()
saveHaskellModule mn body =
let sources = T.unlines body
saveModule file = liftIO (saveFile file >> stylishHaskell file)
saveFile = flip TIO.writeFile sources
stylishHaskell file = callCommand ("stylish-haskell -i " <> file)
doNothing = pure ()
in say ("module " % fModuleName) mn >> mkSourceFile mn >>= maybe doNothing saveModule
mkSourceFile :: ModuleName -> Scaffolding (Maybe FilePath)
mkSourceFile = locateSourceFile >=> traverse prepareHierarchy
type FileInDirectory = (Directory, FileName)
type Directory = FilePath
type FileName = FilePath
prepareHierarchy :: FileInDirectory -> Scaffolding FilePath
prepareHierarchy (directory, file) =
let fullPath = directory </> file
in fullPath <$ liftIO (createDirectoryIfMissing True directory)
locateSourceFile :: ModuleName -> Scaffolding (Maybe FileInDirectory)
locateSourceFile (ModuleName mn) =
let hierarchy = splitOn "." mn
toFile n = n <> ".hs"
path :: Directory -> Maybe (Directory, FileName)
path directory = fmap toFile . swap . fmap (foldl (</>) directory . reverse) <$> uncons (reverse hierarchy)
in asks (path . targetDirectory)
includeSpecification :: FilePath -> Scaffolding [Text]
includeSpecification = fmap (formatSpecification . T.lines) . liftIO . TIO.readFile
type ElementWithDefinition elt = (FilePath, elt)
scaffoldElements :: (NonEmpty (ElementWithDefinition element) -> Scaffolding ())
-> ( ElementWithDefinition element -> Scaffolding ())
-> ([ ElementWithDefinition element] -> Scaffolding ())
scaffoldElements parentScaffolder elementScaffolder =
let doNothing = pure ()
scaffolder elts = parentScaffolder elts >> traverse_ elementScaffolder elts
in maybe doNothing scaffolder . nonEmpty
parentModule :: ModuleName -> ModuleAlias -> (ModuleName -> element -> ModuleName) -> NonEmpty (ElementWithDefinition element) -> Scaffolding ()
parentModule elementName alias nameModule elements =
getRootModuleNameFor elementName >>= generateRootModule alias nameModule (snd <$> elements)
generateRootModule :: ModuleAlias -> (ModuleName -> element -> ModuleName) -> NonEmpty element -> ModuleName -> Scaffolding ()
generateRootModule alias nameModule codes mn =
let importElement code = ImportAll (ImportAs (nameModule mn code) alias)
imports = [ ImportGroup (importElement <$> codes) ]
exports = [ reexportAlias alias ]
in saveHaskellModule mn $
moduleDeclaration mn exports imports
haddockDependencies :: Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding [Text]
haddockDependencies formatter elts =
let formattedDependencies = commaSeparated . fmap (sformat (simpleQuoted formatter))
formatHaddock = sformat ("-- Dependencies: " % F.stext % ".")
in pure [ "--"
, formatHaddock (formattedDependencies elts)
]
reexportDependencies :: Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding [Export]
reexportDependencies formatter =
let mkReexport = Name . sformat formatter
prependTitle = NE.cons (Comment "* Dependencies")
in pure . NE.toList . prependTitle . fmap mkReexport
importDependencies :: ModuleName -> Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding Import
importDependencies moduleName formatter elts =
let imports = NE.toList (sformat formatter <$> elts)
mkImport mn = Import (BasicImport mn) imports
in mkImport <$> getRootModuleNameFor moduleName
importCombinators :: ImportGroup
importCombinators =
ImportGroup
[ ImportAll "Text.Edifact.Parsing"
, Import "Text.Edifact.Types" [ "Value" ]
]
importNotYetImplementedHelper :: ImportGroup
importNotYetImplementedHelper =
ImportGroup
[ Import "Text.Edifact.Parsing.Commons" [ "notYetImplemented" ]
]
moduleDeclaration :: ModuleName -> [Export] -> [ImportGroup] -> [Text]
moduleDeclaration moduleName exports imports =
let decl mn [] = [sformat ("module " % fModuleName % " where") mn]
decl mn ex = sformat ("module " % fModuleName) mn
: renderExports ex
in intercalate newline [ decl moduleName exports
, renderImports imports
]
machineGeneratedWarning :: [Text]
machineGeneratedWarning =
[ "---- Machine generated code."
, "---- Output of edi-parser-scaffolder"
]
scaffoldModule :: ModuleName -> [LanguageExtension] -> [Export] -> [ImportGroup] -> [Text] -> Scaffolding ()
scaffoldModule mn exts exports imports code =
saveHaskellModule mn $
intercalate newline
[ extensions exts
, machineGeneratedWarning
, moduleDeclaration mn exports imports
, code
]
renderExports :: [Export] -> [Text]
renderExports exports =
let formatExport (First e) = sformat (" " % fExport) e
formatExport (Following e) = sformat (", " % fExport) e
formatExport (Skipped e) = sformat (" " % fExport) e
fExport =
let f (Comment t) = bprint ("-- " % stext) t
f (Name t) = bprint stext t
in later f
parensOnFirstLine [] = []
parensOnFirstLine (firstLine : rest) = ("(" <> T.drop 1 firstLine) : rest
ls = parensOnFirstLine (formatExport <$> tag exports) <> [ ") where" ]
in indent <$> ls
data Export = Name Text
| Comment Text
instance IsString Export where
fromString = Name . fromString
data Tag a = First a
| Following a
| Skipped a
tag :: [Export] -> [Tag Export]
tag =
let skipAll = fmap Skipped
tagFirst [] = []
tagFirst (elt : others) = First elt : tagOthers others
tagOthers = fmap tagOther
tagOther v | isComment v = Skipped v
| otherwise = Following v
merge (xs, ys) = xs <> ys
in merge . bimap skipAll tagFirst . span isComment
isComment :: Export -> Bool
isComment (Comment _) = True
isComment _ = False
newtype ModuleAlias = ModuleAlias { getModuleAlias :: Text } deriving newtype (IsString)
singleImport :: Import -> ImportGroup
singleImport = ImportGroup . pure
newtype ImportGroup = ImportGroup (NonEmpty Import) deriving newtype Semigroup
data Import = Import ImportName [Text]
| ImportAll ImportName
data ImportName = BasicImport ModuleName
| ImportAs ModuleName ModuleAlias
| ImportQualified ModuleName
| ImportQualifiedAs ModuleName ModuleAlias
instance IsString ImportName where
fromString = BasicImport . fromString
renderImports :: [ImportGroup] -> [Text]
renderImports = intercalate newline . fmap renderImportGroup
reexportAlias :: ModuleAlias -> Export
reexportAlias = Name . sformat ("module " % fModuleAlias)
renderImportGroup :: ImportGroup -> [Text]
renderImportGroup (ImportGroup imports) = NE.toList (renderImport <$> imports)
renderImport :: Import -> Text
renderImport (ImportAll name) = sformat fImportName name
renderImport (Import name references) =
sformat (fImportName % " " % parens stext) name (commaSeparated references)
fImportName :: Format r (ImportName -> r)
fImportName =
let
build (BasicImport name) = bprint ("import " % fModuleName) name
build (ImportAs name alias) = bprint ("import " % fModuleName % " as " % fModuleAlias) name alias
build (ImportQualified name) = bprint ("import qualified " % fModuleName) name
build (ImportQualifiedAs name alias) = bprint ("import qualified " % fModuleName % " as " % fModuleAlias) name alias
in later build
fModuleAlias :: Format r (ModuleAlias -> r)
fModuleAlias = mapf getModuleAlias stext