aboutsummaryrefslogtreecommitdiffhomepage
path: root/scaffolder/src/Text/Edifact/Scaffolder/Commons/Language.hs
diff options
context:
space:
mode:
authorFrédéric Menou <frederic.menou@fretlink.com>2016-12-08 10:19:15 +0200
committerIsmaël Bouya <ismael.bouya@fretlink.com>2022-05-17 18:01:51 +0200
commita9d77a20008efe82862cc1adbfa7a6d4f09f8ff7 (patch)
treeadf3186fdccaeef19151026cdfbd38a530cf9ecb /scaffolder/src/Text/Edifact/Scaffolder/Commons/Language.hs
downloadedi-parser-a9d77a20008efe82862cc1adbfa7a6d4f09f8ff7.tar.gz
edi-parser-a9d77a20008efe82862cc1adbfa7a6d4f09f8ff7.tar.zst
edi-parser-a9d77a20008efe82862cc1adbfa7a6d4f09f8ff7.zip
Release code as open sourceHEADmaster
Diffstat (limited to 'scaffolder/src/Text/Edifact/Scaffolder/Commons/Language.hs')
-rw-r--r--scaffolder/src/Text/Edifact/Scaffolder/Commons/Language.hs286
1 files changed, 286 insertions, 0 deletions
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Commons/Language.hs b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Language.hs
new file mode 100644
index 0000000..214ee43
--- /dev/null
+++ b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Language.hs
@@ -0,0 +1,286 @@
1{-# LANGUAGE DerivingStrategies #-}
2{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3{-# LANGUAGE OverloadedLists #-}
4{-# LANGUAGE OverloadedStrings #-}
5
6module Text.Edifact.Scaffolder.Commons.Language
7 ( -- *
8 saveHaskellModule
9 , includeSpecification
10 -- *
11 , scaffoldModule
12 -- *
13 , getRootModuleName
14 , getRootModuleNameFor
15 -- *
16 , scaffoldElements
17 , ElementWithDefinition
18 -- *
19 , parentModule
20 -- *
21 , haddockDependencies
22 -- *
23 , reexportDependencies
24 -- *
25 , importDependencies
26 , importCombinators
27 , importNotYetImplementedHelper
28 -- *
29 , moduleDeclaration
30 , Export(..)
31 -- *
32 , reexportAlias
33 , singleImport
34 , ImportGroup(..)
35 , Import(..)
36 , ImportName(..)
37 , ModuleAlias(..)
38 , LanguageExtension(..)
39 ) where
40
41import Text.Edifact.Scaffolder.Commons.Formatters (fModuleName,
42 parens,
43 simpleQuoted)
44import Text.Edifact.Scaffolder.Commons.Logging (say)
45import Text.Edifact.Scaffolder.Commons.Text (commaSeparated,
46 extensions,
47 formatSpecification,
48 indent, newline)
49import Text.Edifact.Scaffolder.Commons.Types (LanguageExtension (..),
50 ModuleName (..),
51 Scaffolding,
52 getRevision,
53 hostModule,
54 revision,
55 targetDirectory,
56 (<.>))
57
58import Control.Monad ((>=>))
59import Control.Monad.IO.Class (liftIO)
60import Control.Monad.Reader (asks)
61import Data.Bifunctor (bimap)
62import Data.Foldable (traverse_)
63import Data.List (intercalate,
64 uncons)
65import Data.List.NonEmpty (NonEmpty, nonEmpty)
66import qualified Data.List.NonEmpty as NE (cons, toList)
67import Data.List.Split (splitOn)
68import Data.String (IsString (..))
69import Data.Text as T (Text, drop,
70 lines,
71 unlines)
72import qualified Data.Text.IO as TIO (readFile,
73 writeFile)
74import Data.Tuple (swap)
75import Formatting as F (Format,
76 bprint, later,
77 mapf, sformat,
78 stext, (%))
79import System.Directory (createDirectoryIfMissing)
80import System.FilePath ((</>))
81import System.Process (callCommand)
82
83getRootModuleName :: Scaffolding ModuleName
84getRootModuleName =
85 let prefix host rev = host <.> ModuleName (getRevision rev)
86 in asks (prefix . hostModule) <*> asks revision
87
88getRootModuleNameFor :: ModuleName -> Scaffolding ModuleName
89getRootModuleNameFor name =
90 let suffix root = root <.> name
91 in suffix <$> getRootModuleName
92
93saveHaskellModule :: ModuleName -> [Text] -> Scaffolding ()
94saveHaskellModule 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)
99 doNothing = pure ()
100 in say ("module " % fModuleName) mn >> mkSourceFile mn >>= maybe doNothing saveModule
101
102mkSourceFile :: ModuleName -> Scaffolding (Maybe FilePath)
103mkSourceFile = locateSourceFile >=> traverse prepareHierarchy
104
105type FileInDirectory = (Directory, FileName)
106type Directory = FilePath
107type FileName = FilePath
108
109prepareHierarchy :: FileInDirectory -> Scaffolding FilePath
110prepareHierarchy (directory, file) =
111 let fullPath = directory </> file
112 in fullPath <$ liftIO (createDirectoryIfMissing True directory)
113
114locateSourceFile :: ModuleName -> Scaffolding (Maybe FileInDirectory)
115locateSourceFile (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)
121
122includeSpecification :: FilePath -> Scaffolding [Text]
123includeSpecification = fmap (formatSpecification . T.lines) . liftIO . TIO.readFile
124
125type ElementWithDefinition elt = (FilePath, elt)
126
127scaffoldElements :: (NonEmpty (ElementWithDefinition element) -> Scaffolding ())
128 -> ( ElementWithDefinition element -> Scaffolding ())
129 -> ([ ElementWithDefinition element] -> Scaffolding ())
130scaffoldElements parentScaffolder elementScaffolder =
131 let doNothing = pure ()
132 scaffolder elts = parentScaffolder elts >> traverse_ elementScaffolder elts
133 in maybe doNothing scaffolder . nonEmpty
134
135parentModule :: ModuleName -> ModuleAlias -> (ModuleName -> element -> ModuleName) -> NonEmpty (ElementWithDefinition element) -> Scaffolding ()
136parentModule elementName alias nameModule elements =
137 getRootModuleNameFor elementName >>= generateRootModule alias nameModule (snd <$> elements)
138
139generateRootModule :: ModuleAlias -> (ModuleName -> element -> ModuleName) -> NonEmpty element -> ModuleName -> Scaffolding ()
140generateRootModule 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
146
147haddockDependencies :: Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding [Text]
148haddockDependencies formatter elts =
149 let formattedDependencies = commaSeparated . fmap (sformat (simpleQuoted formatter))
150 formatHaddock = sformat ("-- Dependencies: " % F.stext % ".")
151 in pure [ "--"
152 , formatHaddock (formattedDependencies elts)
153 ]
154
155reexportDependencies :: Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding [Export]
156reexportDependencies formatter =
157 let mkReexport = Name . sformat formatter
158 prependTitle = NE.cons (Comment "* Dependencies")
159 in pure . NE.toList . prependTitle . fmap mkReexport
160
161importDependencies :: ModuleName -> Format Text (elt -> Text) -> NonEmpty elt -> Scaffolding Import
162importDependencies moduleName formatter elts =
163 let imports = NE.toList (sformat formatter <$> elts)
164 mkImport mn = Import (BasicImport mn) imports
165 in mkImport <$> getRootModuleNameFor moduleName
166
167importCombinators :: ImportGroup
168importCombinators =
169 ImportGroup
170 [ ImportAll "Text.Edifact.Parsing"
171 , Import "Text.Edifact.Types" [ "Value" ]
172 ]
173
174importNotYetImplementedHelper :: ImportGroup
175importNotYetImplementedHelper =
176 ImportGroup
177 [ Import "Text.Edifact.Parsing.Commons" [ "notYetImplemented" ]
178 ]
179
180moduleDeclaration :: ModuleName -> [Export] -> [ImportGroup] -> [Text]
181moduleDeclaration moduleName exports imports =
182 let decl mn [] = [sformat ("module " % fModuleName % " where") mn]
183 decl mn ex = sformat ("module " % fModuleName) mn
184 : renderExports ex
185 in intercalate newline [ decl moduleName exports
186 , renderImports imports
187 ]
188
189machineGeneratedWarning :: [Text]
190machineGeneratedWarning =
191 [ "---- Machine generated code."
192 , "---- Output of edi-parser-scaffolder"
193 ]
194
195scaffoldModule :: ModuleName -> [LanguageExtension] -> [Export] -> [ImportGroup] -> [Text] -> Scaffolding ()
196scaffoldModule mn exts exports imports code =
197 saveHaskellModule mn $
198 intercalate newline
199 [ extensions exts
200 , machineGeneratedWarning
201 , moduleDeclaration mn exports imports
202 , code
203 ]
204
205renderExports :: [Export] -> [Text]
206renderExports exports =
207 let formatExport (First e) = sformat (" " % fExport) e
208 formatExport (Following e) = sformat (", " % fExport) e
209 formatExport (Skipped e) = sformat (" " % fExport) e
210 fExport =
211 let f (Comment t) = bprint ("-- " % stext) t
212 f (Name t) = bprint stext t
213 in later f
214 parensOnFirstLine [] = []
215 parensOnFirstLine (firstLine : rest) = ("(" <> T.drop 1 firstLine) : rest
216 ls = parensOnFirstLine (formatExport <$> tag exports) <> [ ") where" ]
217 in indent <$> ls
218
219data Export = Name Text
220 | Comment Text
221
222instance IsString Export where
223 fromString = Name . fromString
224
225data Tag a = First a
226 | Following a
227 | Skipped a
228
229tag :: [Export] -> [Tag Export]
230tag =
231 let skipAll = fmap Skipped
232 tagFirst [] = []
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
239
240isComment :: Export -> Bool
241isComment (Comment _) = True
242isComment _ = False
243
244newtype ModuleAlias = ModuleAlias { getModuleAlias :: Text } deriving newtype (IsString)
245
246singleImport :: Import -> ImportGroup
247singleImport = ImportGroup . pure
248
249newtype ImportGroup = ImportGroup (NonEmpty Import) deriving newtype Semigroup
250
251data Import = Import ImportName [Text]
252 | ImportAll ImportName
253
254data ImportName = BasicImport ModuleName
255 | ImportAs ModuleName ModuleAlias
256 | ImportQualified ModuleName
257 | ImportQualifiedAs ModuleName ModuleAlias
258
259instance IsString ImportName where
260 fromString = BasicImport . fromString
261
262renderImports :: [ImportGroup] -> [Text]
263renderImports = intercalate newline . fmap renderImportGroup
264
265reexportAlias :: ModuleAlias -> Export
266reexportAlias = Name . sformat ("module " % fModuleAlias)
267
268renderImportGroup :: ImportGroup -> [Text]
269renderImportGroup (ImportGroup imports) = NE.toList (renderImport <$> imports)
270
271renderImport :: Import -> Text
272renderImport (ImportAll name) = sformat fImportName name
273renderImport (Import name references) =
274 sformat (fImportName % " " % parens stext) name (commaSeparated references)
275
276fImportName :: Format r (ImportName -> r)
277fImportName =
278 let
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
283 in later build
284
285fModuleAlias :: Format r (ModuleAlias -> r)
286fModuleAlias = mapf getModuleAlias stext