1 {-# LANGUAGE DerivingStrategies #-}
2 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
3 {-# LANGUAGE OverloadedLists #-}
4 {-# LANGUAGE OverloadedStrings #-}
6 module Text.Edifact.Scaffolder.Commons.Language
14 , getRootModuleNameFor
17 , ElementWithDefinition
23 , reexportDependencies
27 , importNotYetImplementedHelper
38 , LanguageExtension(..)
41 import Text.Edifact.Scaffolder.Commons.Formatters (fModuleName,
44 import Text.Edifact.Scaffolder.Commons.Logging (say)
45 import Text.Edifact.Scaffolder.Commons.Text (commaSeparated,
49 import Text.Edifact.Scaffolder.Commons.Types (LanguageExtension (..),
58 import Control.Monad ((>=>))
59 import Control.Monad.IO.Class (liftIO)
60 import Control.Monad.Reader (asks)
61 import Data.Bifunctor (bimap)
62 import Data.Foldable (traverse_)
63 import Data.List (intercalate,
65 import Data.List.NonEmpty (NonEmpty, nonEmpty)
66 import qualified Data.List.NonEmpty as NE (cons, toList)
67 import Data.List.Split (splitOn)
68 import Data.String (IsString (..))
69 import Data.Text as T (Text, drop,
72 import qualified Data.Text.IO as TIO (readFile,
74 import Data.Tuple (swap)
75 import Formatting as F (Format,
79 import System.Directory (createDirectoryIfMissing)
80 import System.FilePath ((</>))
81 import System.Process (callCommand)
83 getRootModuleName :: Scaffolding ModuleName
85 let prefix host rev = host <.> ModuleName (getRevision rev)
86 in asks (prefix . hostModule) <*> asks revision
88 getRootModuleNameFor :: ModuleName -> Scaffolding ModuleName
89 getRootModuleNameFor name =
90 let suffix root = root <.> name
91 in suffix <$> getRootModuleName
93 saveHaskellModule :: ModuleName -> [Text] -> Scaffolding ()
94 saveHaskellModule mn body =
95 let sources = T.unlines body
96 saveModule file = liftIO (saveFile file >> stylishHaskell file)
97 saveFile = flip TIO.writeFile sources
98 stylishHaskell file = callCommand ("stylish-haskell -i " <> file)
100 in say ("module " % fModuleName) mn >> mkSourceFile mn >>= maybe doNothing saveModule
102 mkSourceFile :: ModuleName -> Scaffolding (Maybe FilePath)
103 mkSourceFile = locateSourceFile >=> traverse prepareHierarchy
105 type FileInDirectory = (Directory, FileName)
106 type Directory = FilePath
107 type FileName = FilePath
109 prepareHierarchy :: FileInDirectory -> Scaffolding FilePath
110 prepareHierarchy (directory, file) =
111 let fullPath = directory </> file
112 in fullPath <$ liftIO (createDirectoryIfMissing True directory)
114 locateSourceFile :: ModuleName -> Scaffolding (Maybe FileInDirectory)
115 locateSourceFile (ModuleName mn) =
116 let hierarchy = splitOn "." mn
117 toFile n = n <> ".hs"
118 path :: Directory -> Maybe (Directory, FileName)
119 path directory = fmap toFile . swap . fmap (foldl (</>) directory . reverse) <$> uncons (reverse hierarchy)
120 in asks (path . targetDirectory)
122 includeSpecification :: FilePath -> Scaffolding [Text]
123 includeSpecification = fmap (formatSpecification . T.lines) . liftIO . TIO.readFile
125 type ElementWithDefinition elt = (FilePath, elt)
127 scaffoldElements :: (NonEmpty (ElementWithDefinition element) -> Scaffolding ())
128 -> ( ElementWithDefinition element -> Scaffolding ())
129 -> ([ ElementWithDefinition element] -> Scaffolding ())
130 scaffoldElements parentScaffolder elementScaffolder =
131 let doNothing = pure ()
132 scaffolder elts = parentScaffolder elts >> traverse_ elementScaffolder elts
133 in maybe doNothing scaffolder . nonEmpty
135 parentModule :: ModuleName -> ModuleAlias -> (ModuleName -> element -> ModuleName) -> NonEmpty (ElementWithDefinition element) -> Scaffolding ()
136 parentModule elementName alias nameModule elements =
137 getRootModuleNameFor elementName >>= generateRootModule alias nameModule (snd <$> elements)
139 generateRootModule :: ModuleAlias -> (ModuleName -> element -> ModuleName) -> NonEmpty element -> ModuleName -> Scaffolding ()
140 generateRootModule alias nameModule codes mn =
141 let importElement code = ImportAll (ImportAs (nameModule mn code) alias)
142 imports = [ ImportGroup (importElement <$> codes) ]
143 exports = [ reexportAlias alias ]
144 in saveHaskellModule mn $
145 moduleDeclaration mn exports imports
147 haddockDependencies :: Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding [Text]
148 haddockDependencies formatter elts =
149 let formattedDependencies = commaSeparated . fmap (sformat (simpleQuoted formatter))
150 formatHaddock = sformat ("-- Dependencies: " % F.stext % ".")
152 , formatHaddock (formattedDependencies elts)
155 reexportDependencies :: Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding [Export]
156 reexportDependencies formatter =
157 let mkReexport = Name . sformat formatter
158 prependTitle = NE.cons (Comment "* Dependencies")
159 in pure . NE.toList . prependTitle . fmap mkReexport
161 importDependencies :: ModuleName -> Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding Import
162 importDependencies moduleName formatter elts =
163 let imports = NE.toList (sformat formatter <$> elts)
164 mkImport mn = Import (BasicImport mn) imports
165 in mkImport <$> getRootModuleNameFor moduleName
167 importCombinators :: ImportGroup
170 [ ImportAll "Text.Edifact.Parsing"
171 , Import "Text.Edifact.Types" [ "Value" ]
174 importNotYetImplementedHelper :: ImportGroup
175 importNotYetImplementedHelper =
177 [ Import "Text.Edifact.Parsing.Commons" [ "notYetImplemented" ]
180 moduleDeclaration :: ModuleName -> [Export] -> [ImportGroup] -> [Text]
181 moduleDeclaration moduleName exports imports =
182 let decl mn [] = [sformat ("module " % fModuleName % " where") mn]
183 decl mn ex = sformat ("module " % fModuleName) mn
185 in intercalate newline [ decl moduleName exports
186 , renderImports imports
189 machineGeneratedWarning :: [Text]
190 machineGeneratedWarning =
191 [ "---- Machine generated code."
192 , "---- Output of edi-parser-scaffolder"
195 scaffoldModule :: ModuleName -> [LanguageExtension] -> [Export] -> [ImportGroup] -> [Text] -> Scaffolding ()
196 scaffoldModule mn exts exports imports code =
197 saveHaskellModule mn $
200 , machineGeneratedWarning
201 , moduleDeclaration mn exports imports
205 renderExports :: [Export] -> [Text]
206 renderExports exports =
207 let formatExport (First e) = sformat (" " % fExport) e
208 formatExport (Following e) = sformat (", " % fExport) e
209 formatExport (Skipped e) = sformat (" " % fExport) e
211 let f (Comment t) = bprint ("-- " % stext) t
212 f (Name t) = bprint stext t
214 parensOnFirstLine [] = []
215 parensOnFirstLine (firstLine : rest) = ("(" <> T.drop 1 firstLine) : rest
216 ls = parensOnFirstLine (formatExport <$> tag exports) <> [ ") where" ]
219 data Export = Name Text
222 instance IsString Export where
223 fromString = Name . fromString
229 tag :: [Export] -> [Tag Export]
231 let skipAll = fmap Skipped
233 tagFirst (elt : others) = First elt : tagOthers others
234 tagOthers = fmap tagOther
235 tagOther v | isComment v = Skipped v
236 | otherwise = Following v
237 merge (xs, ys) = xs <> ys
238 in merge . bimap skipAll tagFirst . span isComment
240 isComment :: Export -> Bool
241 isComment (Comment _) = True
244 newtype ModuleAlias = ModuleAlias { getModuleAlias :: Text } deriving newtype (IsString)
246 singleImport :: Import -> ImportGroup
247 singleImport = ImportGroup . pure
249 newtype ImportGroup = ImportGroup (NonEmpty Import) deriving newtype Semigroup
251 data Import = Import ImportName [Text]
252 | ImportAll ImportName
254 data ImportName = BasicImport ModuleName
255 | ImportAs ModuleName ModuleAlias
256 | ImportQualified ModuleName
257 | ImportQualifiedAs ModuleName ModuleAlias
259 instance IsString ImportName where
260 fromString = BasicImport . fromString
262 renderImports :: [ImportGroup] -> [Text]
263 renderImports = intercalate newline . fmap renderImportGroup
265 reexportAlias :: ModuleAlias -> Export
266 reexportAlias = Name . sformat ("module " % fModuleAlias)
268 renderImportGroup :: ImportGroup -> [Text]
269 renderImportGroup (ImportGroup imports) = NE.toList (renderImport <$> imports)
271 renderImport :: Import -> Text
272 renderImport (ImportAll name) = sformat fImportName name
273 renderImport (Import name references) =
274 sformat (fImportName % " " % parens stext) name (commaSeparated references)
276 fImportName :: Format r (ImportName -> r)
279 build (BasicImport name) = bprint ("import " % fModuleName) name
280 build (ImportAs name alias) = bprint ("import " % fModuleName % " as " % fModuleAlias) name alias
281 build (ImportQualified name) = bprint ("import qualified " % fModuleName) name
282 build (ImportQualifiedAs name alias) = bprint ("import qualified " % fModuleName % " as " % fModuleAlias) name alias
285 fModuleAlias :: Format r (ModuleAlias -> r)
286 fModuleAlias = mapf getModuleAlias stext