diff options
Diffstat (limited to 'scaffolder/src/Text/Edifact/Scaffolder/Commons')
6 files changed, 670 insertions, 0 deletions
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Commons/Formatters.hs b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Formatters.hs new file mode 100644 index 0000000..6f0210b --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Formatters.hs | |||
@@ -0,0 +1,88 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | |||
3 | module Text.Edifact.Scaffolder.Commons.Formatters | ||
4 | ( -- * | ||
5 | fMessageCode | ||
6 | , fMessageParserFunction | ||
7 | , fGroupCode | ||
8 | , fSegmentCode | ||
9 | , fSegmentParserFunction | ||
10 | , fCompositeCode | ||
11 | , fCompositeParserFunction | ||
12 | , fSimpleCode | ||
13 | , fSimpleParserFunction | ||
14 | |||
15 | -- * | ||
16 | , fParserSignature | ||
17 | , fParserDeclaration | ||
18 | -- * | ||
19 | , fModuleName | ||
20 | , fPosition | ||
21 | , fPresence | ||
22 | -- * | ||
23 | , quoted | ||
24 | , simpleQuoted | ||
25 | , parens | ||
26 | , notYetImplemented | ||
27 | ) where | ||
28 | |||
29 | import Text.Edifact.Scaffolder.Commons.Types | ||
30 | |||
31 | import Formatting as F | ||
32 | |||
33 | fMessageCode:: Format r (MessageCode -> r) | ||
34 | fMessageCode = mapf getMessageCode F.string | ||
35 | |||
36 | fMessageParserFunction :: Format r (MessageCode -> r) | ||
37 | fMessageParserFunction = mapf getMessageCode ("message" % F.string) | ||
38 | |||
39 | fGroupCode :: Format r (GroupCode -> r) | ||
40 | fGroupCode = mapf getGroupCode F.string | ||
41 | |||
42 | fSegmentCode :: Format r (SegmentCode -> r) | ||
43 | fSegmentCode = mapf getSegmentCode F.string | ||
44 | |||
45 | fSegmentParserFunction :: Format r (SegmentCode -> r) | ||
46 | fSegmentParserFunction = mapf getSegmentCode ("segment" % F.string) | ||
47 | |||
48 | fCompositeCode :: Format r (CompositeCode -> r) | ||
49 | fCompositeCode = mapf getCompositeCode F.string | ||
50 | |||
51 | fCompositeParserFunction :: Format r (CompositeCode -> r) | ||
52 | fCompositeParserFunction = mapf getCompositeCode ("composite" % F.string) | ||
53 | |||
54 | fSimpleCode :: Format r (SimpleCode -> r) | ||
55 | fSimpleCode = mapf getSimpleCode F.string | ||
56 | |||
57 | fSimpleParserFunction :: Format r (SimpleCode -> r) | ||
58 | fSimpleParserFunction = mapf getSimpleCode ("simple" % F.string) | ||
59 | |||
60 | fParserSignature :: Format r a -> Format r a | ||
61 | fParserSignature f = f % " :: Parser Value" | ||
62 | |||
63 | fParserDeclaration :: Format r a -> Format r a | ||
64 | fParserDeclaration f = f % " =" | ||
65 | |||
66 | fModuleName :: Format r (ModuleName -> r) | ||
67 | fModuleName = mapf getModuleName string | ||
68 | |||
69 | fPosition :: Format r (Position -> r) | ||
70 | fPosition = mapf getPosition F.string | ||
71 | |||
72 | fPresence :: Format r (Presence -> r) | ||
73 | fPresence = | ||
74 | let f Mandatory = "mandatory" | ||
75 | f Optional = "optional " | ||
76 | in mapf f F.string | ||
77 | |||
78 | quoted :: Format r a -> Format r a | ||
79 | quoted f = "\"" % f % "\"" | ||
80 | |||
81 | simpleQuoted :: Format r a -> Format r a | ||
82 | simpleQuoted f = "'" % f % "'" | ||
83 | |||
84 | parens :: Format r a -> Format r a | ||
85 | parens f = "(" % f % ")" | ||
86 | |||
87 | notYetImplemented :: Format r a -> Format r a | ||
88 | notYetImplemented desc = "notYetImplemented " % quoted (desc % " not yet implemented") | ||
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 | |||
6 | module 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 | |||
41 | import Text.Edifact.Scaffolder.Commons.Formatters (fModuleName, | ||
42 | parens, | ||
43 | simpleQuoted) | ||
44 | import Text.Edifact.Scaffolder.Commons.Logging (say) | ||
45 | import Text.Edifact.Scaffolder.Commons.Text (commaSeparated, | ||
46 | extensions, | ||
47 | formatSpecification, | ||
48 | indent, newline) | ||
49 | import Text.Edifact.Scaffolder.Commons.Types (LanguageExtension (..), | ||
50 | ModuleName (..), | ||
51 | Scaffolding, | ||
52 | getRevision, | ||
53 | hostModule, | ||
54 | revision, | ||
55 | targetDirectory, | ||
56 | (<.>)) | ||
57 | |||
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, | ||
64 | uncons) | ||
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, | ||
70 | lines, | ||
71 | unlines) | ||
72 | import qualified Data.Text.IO as TIO (readFile, | ||
73 | writeFile) | ||
74 | import Data.Tuple (swap) | ||
75 | import Formatting as F (Format, | ||
76 | bprint, later, | ||
77 | mapf, sformat, | ||
78 | stext, (%)) | ||
79 | import System.Directory (createDirectoryIfMissing) | ||
80 | import System.FilePath ((</>)) | ||
81 | import System.Process (callCommand) | ||
82 | |||
83 | getRootModuleName :: Scaffolding ModuleName | ||
84 | getRootModuleName = | ||
85 | let prefix host rev = host <.> ModuleName (getRevision rev) | ||
86 | in asks (prefix . hostModule) <*> asks revision | ||
87 | |||
88 | getRootModuleNameFor :: ModuleName -> Scaffolding ModuleName | ||
89 | getRootModuleNameFor name = | ||
90 | let suffix root = root <.> name | ||
91 | in suffix <$> getRootModuleName | ||
92 | |||
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) | ||
99 | doNothing = pure () | ||
100 | in say ("module " % fModuleName) mn >> mkSourceFile mn >>= maybe doNothing saveModule | ||
101 | |||
102 | mkSourceFile :: ModuleName -> Scaffolding (Maybe FilePath) | ||
103 | mkSourceFile = locateSourceFile >=> traverse prepareHierarchy | ||
104 | |||
105 | type FileInDirectory = (Directory, FileName) | ||
106 | type Directory = FilePath | ||
107 | type FileName = FilePath | ||
108 | |||
109 | prepareHierarchy :: FileInDirectory -> Scaffolding FilePath | ||
110 | prepareHierarchy (directory, file) = | ||
111 | let fullPath = directory </> file | ||
112 | in fullPath <$ liftIO (createDirectoryIfMissing True directory) | ||
113 | |||
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) | ||
121 | |||
122 | includeSpecification :: FilePath -> Scaffolding [Text] | ||
123 | includeSpecification = fmap (formatSpecification . T.lines) . liftIO . TIO.readFile | ||
124 | |||
125 | type ElementWithDefinition elt = (FilePath, elt) | ||
126 | |||
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 | ||
134 | |||
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) | ||
138 | |||
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 | ||
146 | |||
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 % ".") | ||
151 | in pure [ "--" | ||
152 | , formatHaddock (formattedDependencies elts) | ||
153 | ] | ||
154 | |||
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 | ||
160 | |||
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 | ||
166 | |||
167 | importCombinators :: ImportGroup | ||
168 | importCombinators = | ||
169 | ImportGroup | ||
170 | [ ImportAll "Text.Edifact.Parsing" | ||
171 | , Import "Text.Edifact.Types" [ "Value" ] | ||
172 | ] | ||
173 | |||
174 | importNotYetImplementedHelper :: ImportGroup | ||
175 | importNotYetImplementedHelper = | ||
176 | ImportGroup | ||
177 | [ Import "Text.Edifact.Parsing.Commons" [ "notYetImplemented" ] | ||
178 | ] | ||
179 | |||
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 | ||
184 | : renderExports ex | ||
185 | in intercalate newline [ decl moduleName exports | ||
186 | , renderImports imports | ||
187 | ] | ||
188 | |||
189 | machineGeneratedWarning :: [Text] | ||
190 | machineGeneratedWarning = | ||
191 | [ "---- Machine generated code." | ||
192 | , "---- Output of edi-parser-scaffolder" | ||
193 | ] | ||
194 | |||
195 | scaffoldModule :: ModuleName -> [LanguageExtension] -> [Export] -> [ImportGroup] -> [Text] -> Scaffolding () | ||
196 | scaffoldModule 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 | |||
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 | ||
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 | |||
219 | data Export = Name Text | ||
220 | | Comment Text | ||
221 | |||
222 | instance IsString Export where | ||
223 | fromString = Name . fromString | ||
224 | |||
225 | data Tag a = First a | ||
226 | | Following a | ||
227 | | Skipped a | ||
228 | |||
229 | tag :: [Export] -> [Tag Export] | ||
230 | tag = | ||
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 | |||
240 | isComment :: Export -> Bool | ||
241 | isComment (Comment _) = True | ||
242 | isComment _ = False | ||
243 | |||
244 | newtype ModuleAlias = ModuleAlias { getModuleAlias :: Text } deriving newtype (IsString) | ||
245 | |||
246 | singleImport :: Import -> ImportGroup | ||
247 | singleImport = ImportGroup . pure | ||
248 | |||
249 | newtype ImportGroup = ImportGroup (NonEmpty Import) deriving newtype Semigroup | ||
250 | |||
251 | data Import = Import ImportName [Text] | ||
252 | | ImportAll ImportName | ||
253 | |||
254 | data ImportName = BasicImport ModuleName | ||
255 | | ImportAs ModuleName ModuleAlias | ||
256 | | ImportQualified ModuleName | ||
257 | | ImportQualifiedAs ModuleName ModuleAlias | ||
258 | |||
259 | instance IsString ImportName where | ||
260 | fromString = BasicImport . fromString | ||
261 | |||
262 | renderImports :: [ImportGroup] -> [Text] | ||
263 | renderImports = intercalate newline . fmap renderImportGroup | ||
264 | |||
265 | reexportAlias :: ModuleAlias -> Export | ||
266 | reexportAlias = Name . sformat ("module " % fModuleAlias) | ||
267 | |||
268 | renderImportGroup :: ImportGroup -> [Text] | ||
269 | renderImportGroup (ImportGroup imports) = NE.toList (renderImport <$> imports) | ||
270 | |||
271 | renderImport :: Import -> Text | ||
272 | renderImport (ImportAll name) = sformat fImportName name | ||
273 | renderImport (Import name references) = | ||
274 | sformat (fImportName % " " % parens stext) name (commaSeparated references) | ||
275 | |||
276 | fImportName :: Format r (ImportName -> r) | ||
277 | fImportName = | ||
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 | |||
285 | fModuleAlias :: Format r (ModuleAlias -> r) | ||
286 | fModuleAlias = mapf getModuleAlias stext | ||
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Commons/Logging.hs b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Logging.hs new file mode 100644 index 0000000..1287f7f --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Logging.hs | |||
@@ -0,0 +1,11 @@ | |||
1 | module Text.Edifact.Scaffolder.Commons.Logging | ||
2 | ( say | ||
3 | ) where | ||
4 | |||
5 | import Control.Monad.IO.Class (MonadIO, liftIO) | ||
6 | import qualified Data.Text.Lazy.Builder as TLB (toLazyText) | ||
7 | import qualified Data.Text.Lazy.IO as TLIO (putStrLn) | ||
8 | import Formatting as F (Format, runFormat) | ||
9 | |||
10 | say :: MonadIO m => Format (m ()) a -> a | ||
11 | say m = runFormat m (liftIO . TLIO.putStrLn . TLB.toLazyText) | ||
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Commons/Parsing.hs b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Parsing.hs new file mode 100644 index 0000000..24f8f80 --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Parsing.hs | |||
@@ -0,0 +1,122 @@ | |||
1 | {-# LANGUAGE FlexibleContexts #-} | ||
2 | {-# LANGUAGE TupleSections #-} | ||
3 | |||
4 | module Text.Edifact.Scaffolder.Commons.Parsing | ||
5 | ( -- * | ||
6 | maybeParse | ||
7 | , skipBeginning | ||
8 | , single | ||
9 | , silent | ||
10 | -- * | ||
11 | , listElements | ||
12 | -- * | ||
13 | , presenceParser | ||
14 | , stringToPresenceParser | ||
15 | -- * | ||
16 | , messageCodeParser | ||
17 | -- * | ||
18 | , scanDependencies | ||
19 | , scan | ||
20 | , scanUntil | ||
21 | ) where | ||
22 | |||
23 | import Text.Edifact.Scaffolder.Commons.Logging (say) | ||
24 | import Text.Edifact.Scaffolder.Commons.Types | ||
25 | |||
26 | import Control.Monad.IO.Class (liftIO) | ||
27 | import Control.Monad.Identity (Identity) | ||
28 | import Control.Monad.Reader (asks, local) | ||
29 | import Data.Bifunctor (first) | ||
30 | import Data.List (sort) | ||
31 | import Data.List.NonEmpty (NonEmpty, nonEmpty) | ||
32 | import Data.Maybe (catMaybes) | ||
33 | import Data.String (fromString) | ||
34 | import Data.Text (Text) | ||
35 | import Formatting as F (shown) | ||
36 | import System.Directory (listDirectory) | ||
37 | import System.FilePath ((</>)) | ||
38 | import Text.Parsec (Parsec, SourceName, | ||
39 | Stream, anyChar, char, | ||
40 | choice, count, | ||
41 | endOfLine, eof, | ||
42 | lookAhead, many, | ||
43 | many1, manyTill, | ||
44 | oneOf, optionMaybe, | ||
45 | runParser, string, | ||
46 | try, upper, (<?>)) | ||
47 | |||
48 | maybeParse :: (Show a, Stream s Identity t, Monoid u) => SourceName -> Parsec s u a -> s -> Scaffolding (Maybe a) | ||
49 | maybeParse source parser input = | ||
50 | let interpretParsingResult (Right v) _ = pure (Just v) | ||
51 | interpretParsingResult e True = Nothing <$ say shown e | ||
52 | interpretParsingResult _ False = pure Nothing | ||
53 | shouldDebug = asks debugParsing | ||
54 | in shouldDebug >>= interpretParsingResult (runParser parser mempty source input) | ||
55 | |||
56 | -- | Disable parsing error logging locally | ||
57 | silent :: Scaffolding a -> Scaffolding a | ||
58 | silent = local disableDebugging | ||
59 | |||
60 | -- | Let you traverse a directory and filter files matching a parser. | ||
61 | -- The filename is then paired with the matched value | ||
62 | listElements :: (Show elt, Ord elt) => FilePath -> Parsec String () elt -> Scaffolding [(FilePath, elt)] | ||
63 | listElements subpath parser = do | ||
64 | home <- getSpecificationHome | ||
65 | let directory = home </> subpath | ||
66 | files <- sort <$> liftIO (listDirectory directory) | ||
67 | let prependDirectory f = directory </> f | ||
68 | fmap (first prependDirectory) . catMaybes <$> traverse (extractElement parser) files | ||
69 | |||
70 | getSpecificationHome :: Scaffolding FilePath | ||
71 | getSpecificationHome = | ||
72 | let concatenate path (Revision rev) = path </> rev | ||
73 | in asks (concatenate . specificationsHome) <*> asks revision | ||
74 | |||
75 | extractElement :: Show elt => Parsec String () elt -> FilePath -> Scaffolding (Maybe (FilePath, elt)) | ||
76 | extractElement parser path = silent (fmap (path,) <$> maybeParse path parser path) | ||
77 | |||
78 | skipBeginning :: Stream s Identity Char => Parsec s () a -> Parsec s () a | ||
79 | skipBeginning p = manyTill anyChar (try (lookAhead p)) *> p | ||
80 | |||
81 | single :: Stream s Identity Char => Parsec s u a -> Parsec s u [a] | ||
82 | single = count 1 | ||
83 | |||
84 | presenceParser :: Stream s Identity Char => Parsec s u Presence | ||
85 | presenceParser = | ||
86 | choice [ Mandatory <$ char 'M' | ||
87 | , Optional <$ char 'C' | ||
88 | ] <?> "Presence" | ||
89 | |||
90 | stringToPresenceParser :: Stream s Identity Char => Parsec s u Text | ||
91 | stringToPresenceParser = fromString <$> | ||
92 | manyTill anyChar (try $ lookAhead $ many1 (string " ") >> presenceParser >> string " " >> many (oneOf " 0123456789")) | ||
93 | <?> "Description" | ||
94 | |||
95 | messageCodeParser :: Stream s Identity Char => Parsec s u MessageCode | ||
96 | messageCodeParser = fromString <$> count 6 upper | ||
97 | |||
98 | scanDependencies :: (Monoid u, Show result) => FilePath -> Parsec String u [result] -> Scaffolding (Maybe (NonEmpty result)) | ||
99 | scanDependencies file parser = | ||
100 | let readLines = liftIO (readFile file) | ||
101 | in readLines >>= fmap (nonEmpty =<<) . maybeParse file parser | ||
102 | |||
103 | scan :: Stream s Identity Char => [Parsec s u [a]] -> Parsec s u [a] | ||
104 | scan scanners = | ||
105 | let parsers = (scanLine <$> scanners) <> [skipLine] | ||
106 | end = choice [ () <$ try endOfLine | ||
107 | , () <$ eof | ||
108 | ] | ||
109 | scanLine p = optionMaybe (try p) <* end | ||
110 | skipLine = Nothing <$ manyTill anyChar end | ||
111 | in concat . catMaybes <$> manyTill (choice parsers) eof | ||
112 | |||
113 | scanUntil :: Stream s Identity Char => [Parsec s u a] -> Parsec s u a | ||
114 | scanUntil scanners = | ||
115 | let parsers = scanLine <$> scanners | ||
116 | end = choice [ () <$ try endOfLine | ||
117 | , () <$ eof | ||
118 | ] | ||
119 | searching = choice $ fmap (() <$) parsers <> [ () <$ eof ] | ||
120 | scanLine p = p <* end | ||
121 | skipLine = manyTill anyChar end | ||
122 | in manyTill skipLine (try $ lookAhead searching) >> try (choice parsers) | ||
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Commons/Text.hs b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Text.hs new file mode 100644 index 0000000..ef4e805 --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Text.hs | |||
@@ -0,0 +1,91 @@ | |||
1 | {-# LANGUAGE DerivingStrategies #-} | ||
2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
3 | {-# LANGUAGE OverloadedStrings #-} | ||
4 | |||
5 | module Text.Edifact.Scaffolder.Commons.Text | ||
6 | ( -- * | ||
7 | indent | ||
8 | , quote | ||
9 | , haskellList | ||
10 | , commaSeparated | ||
11 | -- * | ||
12 | , newline | ||
13 | -- * | ||
14 | , formatSpecification | ||
15 | -- * | ||
16 | , extensions | ||
17 | ) where | ||
18 | |||
19 | |||
20 | import Text.Edifact.Scaffolder.Commons.Types (LanguageExtension (..)) | ||
21 | |||
22 | import Control.Category ((>>>)) | ||
23 | import Data.Char (isSpace) | ||
24 | import Data.List (dropWhileEnd) | ||
25 | import Data.String (IsString) | ||
26 | import Data.Text (Text) | ||
27 | import qualified Data.Text as T (all, dropWhileEnd, | ||
28 | null) | ||
29 | import Formatting as F (mapf, sformat, | ||
30 | stext, string, (%)) | ||
31 | |||
32 | formatSpecification :: [Text] -> [Text] | ||
33 | formatSpecification = cleanEmptyLines | ||
34 | >>> fmap quoteLine | ||
35 | >>> prependQuote | ||
36 | |||
37 | prependQuote :: [Text] -> [Text] | ||
38 | prependQuote ls = | ||
39 | [ "-- | Derived from this specification:" | ||
40 | , "--" | ||
41 | ] <> ls | ||
42 | |||
43 | cleanEmptyLines :: [Text] -> [Text] | ||
44 | cleanEmptyLines = dropWhile blank >>> dropWhileEnd blank | ||
45 | |||
46 | blank :: Text -> Bool | ||
47 | blank t = T.null t || T.all isSpace t | ||
48 | |||
49 | quoteLine :: Text -> Text | ||
50 | quoteLine = haskellQuote >>> cleanWhitespaces | ||
51 | |||
52 | haskellQuote :: Text -> Text | ||
53 | haskellQuote line = "-- > " <> line | ||
54 | |||
55 | cleanWhitespaces :: Text -> Text | ||
56 | cleanWhitespaces = T.dropWhileEnd (== ' ') | ||
57 | |||
58 | indent :: Text -> Text | ||
59 | indent t = " " <> t | ||
60 | |||
61 | quote :: Text -> Text | ||
62 | quote t = "'" <> t <> "'" | ||
63 | |||
64 | haskellList :: [Text] -> [Text] | ||
65 | haskellList = | ||
66 | let prefix :: Int -> Text -> Text | ||
67 | prefix 1 dep = sformat ("[ " % F.stext) dep | ||
68 | prefix _ dep = sformat (", " % F.stext) dep | ||
69 | suffix deps = deps <> ["]"] | ||
70 | in suffix . zipWith prefix [1..] | ||
71 | |||
72 | newtype CommaSeparated = CommaSeparated { getCommaSeparated :: Text } deriving newtype (IsString, Eq) | ||
73 | |||
74 | instance Semigroup CommaSeparated where | ||
75 | t1 <> "" = t1 | ||
76 | "" <> t2 = t2 | ||
77 | t1 <> t2 = CommaSeparated (getCommaSeparated t1 <> ", " <> getCommaSeparated t2) | ||
78 | |||
79 | instance Monoid CommaSeparated where | ||
80 | mempty = "" | ||
81 | |||
82 | commaSeparated :: Foldable f => f Text -> Text | ||
83 | commaSeparated = getCommaSeparated . foldMap CommaSeparated | ||
84 | |||
85 | newline :: [Text] | ||
86 | newline = [""] | ||
87 | |||
88 | extensions :: [LanguageExtension] -> [Text] | ||
89 | extensions = | ||
90 | let fExtension = "{-# LANGUAGE " % mapf getLanguageExtension F.string % " #-}" | ||
91 | in fmap (sformat fExtension) | ||
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Commons/Types.hs b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Types.hs new file mode 100644 index 0000000..4d1c0a6 --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Commons/Types.hs | |||
@@ -0,0 +1,72 @@ | |||
1 | {-# LANGUAGE DerivingStrategies #-} | ||
2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
3 | |||
4 | module Text.Edifact.Scaffolder.Commons.Types | ||
5 | ( -- * Codes of elements | ||
6 | MessageCode(..) | ||
7 | , GroupCode(..) | ||
8 | , SegmentCode(..) | ||
9 | , SegmentName(..) | ||
10 | , CompositeCode (..) | ||
11 | , CompositeName (..) | ||
12 | , SimpleCode(..) | ||
13 | , SimpleName(..) | ||
14 | -- * Ordering of elements | ||
15 | , Position(..) | ||
16 | -- * Attributes | ||
17 | , Presence(..) | ||
18 | -- * | ||
19 | , ModuleName(..) | ||
20 | , (<.>) | ||
21 | -- * | ||
22 | , LanguageExtension(..) | ||
23 | -- * | ||
24 | , Scaffolding | ||
25 | , Revision(..) | ||
26 | , ScaffoldingEnv(..) | ||
27 | , disableDebugging | ||
28 | ) where | ||
29 | |||
30 | import Control.Monad.Reader (ReaderT) | ||
31 | import Data.String (IsString) | ||
32 | |||
33 | newtype MessageCode = MessageCode { getMessageCode :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
34 | newtype GroupCode = GroupCode { getGroupCode :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
35 | newtype SegmentCode = SegmentCode { getSegmentCode :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
36 | newtype SegmentName = SegmentName { getSegmentName :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
37 | newtype CompositeCode = CompositeCode { getCompositeCode :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
38 | newtype CompositeName = CompositeName { getCompositeName :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
39 | newtype SimpleCode = SimpleCode { getSimpleCode :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
40 | newtype SimpleName = SimpleName { getSimpleName :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
41 | |||
42 | newtype Position = Position { getPosition :: String } deriving newtype (Show, Eq, Ord, IsString) | ||
43 | |||
44 | data Presence = Mandatory | ||
45 | | Optional | ||
46 | deriving (Show, Eq, Ord) | ||
47 | |||
48 | newtype ModuleName = ModuleName { getModuleName :: String } deriving newtype (Show, Eq, IsString) | ||
49 | |||
50 | instance Semigroup ModuleName where | ||
51 | (<>) = (<.>) | ||
52 | |||
53 | (<.>) :: ModuleName -> ModuleName -> ModuleName | ||
54 | (ModuleName parent) <.> (ModuleName child) = ModuleName (parent <> "." <> child) | ||
55 | |||
56 | newtype LanguageExtension = LanguageExtension { getLanguageExtension :: String } deriving newtype IsString | ||
57 | |||
58 | type Scaffolding = ReaderT ScaffoldingEnv IO | ||
59 | |||
60 | newtype Revision = Revision { getRevision :: String } deriving newtype (Show, Eq, IsString) | ||
61 | |||
62 | data ScaffoldingEnv = | ||
63 | ScaffoldingEnv | ||
64 | { revision :: Revision | ||
65 | , hostModule :: ModuleName | ||
66 | , specificationsHome :: FilePath | ||
67 | , targetDirectory :: FilePath | ||
68 | , debugParsing :: Bool | ||
69 | } | ||
70 | |||
71 | disableDebugging :: ScaffoldingEnv -> ScaffoldingEnv | ||
72 | disableDebugging env = env { debugParsing = False } | ||