diff options
author | Frédéric Menou <frederic.menou@fretlink.com> | 2016-12-08 10:19:15 +0200 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@fretlink.com> | 2022-05-17 18:01:51 +0200 |
commit | a9d77a20008efe82862cc1adbfa7a6d4f09f8ff7 (patch) | |
tree | adf3186fdccaeef19151026cdfbd38a530cf9ecb /scaffolder/src/Text/Edifact/Scaffolder/Messages | |
download | edi-parser-master.tar.gz edi-parser-master.tar.zst edi-parser-master.zip |
Diffstat (limited to 'scaffolder/src/Text/Edifact/Scaffolder/Messages')
5 files changed, 348 insertions, 0 deletions
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Messages/Dependencies.hs b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Dependencies.hs new file mode 100644 index 0000000..fbcc56b --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Dependencies.hs | |||
@@ -0,0 +1,47 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | |||
3 | module Text.Edifact.Scaffolder.Messages.Dependencies | ||
4 | ( -- * | ||
5 | mkDependenciesHaddock | ||
6 | , mkDependenciesImports | ||
7 | , mkDependenciesReexports | ||
8 | ) where | ||
9 | |||
10 | import Text.Edifact.Scaffolder.Commons | ||
11 | import Text.Edifact.Scaffolder.Messages.Types | ||
12 | |||
13 | import Control.Monad ((>=>)) | ||
14 | import Data.List (isPrefixOf) | ||
15 | import Data.List.NonEmpty as NE (nonEmpty, toList) | ||
16 | import Data.Maybe (mapMaybe) | ||
17 | |||
18 | unlessIsCommon :: SegmentCode -> Maybe SegmentCode | ||
19 | unlessIsCommon sc@(SegmentCode code) | "U" `isPrefixOf` code = Nothing | ||
20 | | otherwise = Just sc | ||
21 | |||
22 | mkDependenciesReexports :: NonEmpty Element -> Scaffolding [Export] | ||
23 | mkDependenciesReexports = mkSegmentDependencies mkSegmentDependenciesReexports | ||
24 | |||
25 | mkSegmentDependenciesReexports :: NonEmpty SegmentCode -> Scaffolding [Export] | ||
26 | mkSegmentDependenciesReexports = reexportDependencies fSegmentParserFunction | ||
27 | |||
28 | mkDependenciesImports :: NonEmpty Element -> Scaffolding [ImportGroup] | ||
29 | mkDependenciesImports = mkSegmentDependencies mkSegmentDependenciesImports | ||
30 | |||
31 | mkSegmentDependencies :: (NonEmpty SegmentCode -> Scaffolding [output]) | ||
32 | -> (NonEmpty Element -> Scaffolding [output]) | ||
33 | mkSegmentDependencies mk = maybe (pure []) mk . filterSegmentDependencies | ||
34 | |||
35 | filterSegmentDependencies :: NonEmpty Element -> Maybe (NonEmpty SegmentCode) | ||
36 | filterSegmentDependencies = | ||
37 | fmap nub . nonEmpty . mapMaybe (getSegment >=> unlessIsCommon) . NE.toList | ||
38 | |||
39 | mkSegmentDependenciesImports :: NonEmpty SegmentCode -> Scaffolding [ImportGroup] | ||
40 | mkSegmentDependenciesImports = | ||
41 | fmap (pure . singleImport) . importDependencies "Segments" fSegmentParserFunction | ||
42 | |||
43 | mkDependenciesHaddock :: NonEmpty Element -> Scaffolding [Text] | ||
44 | mkDependenciesHaddock = mkSegmentDependencies mkSegmentDependenciesHaddock | ||
45 | |||
46 | mkSegmentDependenciesHaddock :: NonEmpty SegmentCode -> Scaffolding [Text] | ||
47 | mkSegmentDependenciesHaddock = haddockDependencies fSegmentParserFunction | ||
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Messages/Elements.hs b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Elements.hs new file mode 100644 index 0000000..fb590ad --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Elements.hs | |||
@@ -0,0 +1,22 @@ | |||
1 | module Text.Edifact.Scaffolder.Messages.Elements | ||
2 | ( listMessages | ||
3 | ) where | ||
4 | |||
5 | import Text.Edifact.Scaffolder.Commons | ||
6 | |||
7 | import Data.Char (toUpper) | ||
8 | import Text.Parsec (count, eof, lower, string, | ||
9 | (<?>)) | ||
10 | import Text.Parsec.String (Parser) | ||
11 | |||
12 | -- | List elements | ||
13 | listMessages :: Scaffolding [ElementWithDefinition MessageCode] | ||
14 | listMessages = listElements "messages" messageFilenameParser | ||
15 | |||
16 | messageFilenameParser :: Parser MessageCode | ||
17 | messageFilenameParser = | ||
18 | let mkCode = MessageCode . fmap toUpper | ||
19 | in mkCode <$> count 6 lower | ||
20 | <* string "_s.txt" | ||
21 | <* eof | ||
22 | <?> "MessageCode" | ||
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Messages/Implementation.hs b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Implementation.hs new file mode 100644 index 0000000..121aa45 --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Implementation.hs | |||
@@ -0,0 +1,114 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | |||
3 | module Text.Edifact.Scaffolder.Messages.Implementation | ||
4 | ( -- * | ||
5 | toImplementation | ||
6 | ) where | ||
7 | |||
8 | import Text.Edifact.Scaffolder.Commons | ||
9 | import Text.Edifact.Scaffolder.Messages.Types | ||
10 | |||
11 | import Control.Monad.State.Strict (State, evalState, gets, | ||
12 | modify) | ||
13 | import Data.List.NonEmpty as NE (NonEmpty (..), | ||
14 | fromList, head, | ||
15 | toList, (<|)) | ||
16 | import Formatting | ||
17 | |||
18 | toImplementation :: MessageCode -> NonEmpty Dependency -> [Text] | ||
19 | toImplementation _ = | ||
20 | let closeList deps = deps <> [ "]" ] | ||
21 | in closeList . render . fmap concat . traverse callDependency . NE.toList | ||
22 | |||
23 | render :: Rendering a -> a | ||
24 | render r = | ||
25 | let initialState = RenderingContext 0 0 :| [] | ||
26 | in evalState r initialState | ||
27 | |||
28 | type Trail = NonEmpty | ||
29 | |||
30 | data RenderingContext = RenderingContext { listPosition :: Int | ||
31 | , indentLevel :: Int | ||
32 | } | ||
33 | |||
34 | type Rendering = State (Trail RenderingContext) | ||
35 | |||
36 | callDependency :: Dependency -> Rendering [Text] | ||
37 | callDependency (Dependency element) = renderElement element | ||
38 | |||
39 | increment :: Rendering () | ||
40 | increment = | ||
41 | let mapHead f (v :| t) = f v :| t | ||
42 | in modify (mapHead (\ ctx -> ctx { listPosition = listPosition ctx + 1 })) | ||
43 | |||
44 | pushIndent :: Rendering () | ||
45 | pushIndent = | ||
46 | let indentState t@(previous :| _) = RenderingContext 0 (indentLevel previous + 2) <| t | ||
47 | in modify indentState | ||
48 | |||
49 | popIndent :: Rendering () | ||
50 | popIndent = | ||
51 | let pop (_ :| []) = error "Incoherent state: can't unindent anymore (this shouldn't happen)" | ||
52 | pop (_ :| up) = NE.fromList up | ||
53 | in modify pop | ||
54 | |||
55 | getCurrentIndex :: Rendering Int | ||
56 | getCurrentIndex = gets (listPosition . NE.head) | ||
57 | |||
58 | getCurrentIndentation :: Rendering Int | ||
59 | getCurrentIndentation = gets (indentLevel . NE.head) | ||
60 | |||
61 | renderElement :: Element -> Rendering [Text] | ||
62 | renderElement (Segment code positional) = | ||
63 | let output index indentation = | ||
64 | [ sformat (fIndentation % fIndex % " " % fPositional % " " % fSegmentParserFunction) indentation index positional code | ||
65 | ] | ||
66 | in output <$> getCurrentIndex | ||
67 | <*> getCurrentIndentation | ||
68 | <* increment | ||
69 | renderElement (GroupStart code positional) = | ||
70 | let output index indentation = | ||
71 | [ sformat (fIndentation % fIndex % " " % fPositional % " (") indentation index positional | ||
72 | , sformat (fIndentation % fSegmentGroupFunction) (indentation + 1) code | ||
73 | ] | ||
74 | in output <$> getCurrentIndex | ||
75 | <*> getCurrentIndentation | ||
76 | <* increment | ||
77 | <* pushIndent | ||
78 | renderElement (GroupEnd _) = | ||
79 | let output indentation = | ||
80 | [ sformat (fIndentation % "]") indentation | ||
81 | , sformat (fIndentation % ")") (indentation - 1) | ||
82 | ] | ||
83 | in output <$> getCurrentIndentation | ||
84 | <* popIndent | ||
85 | |||
86 | fIndentation :: Format r (Int -> r) | ||
87 | fIndentation = | ||
88 | let buildIndentation n = fromString (replicate (n * 2) ' ') | ||
89 | in later buildIndentation | ||
90 | |||
91 | fIndex :: Format r (Int -> r) | ||
92 | fIndex = | ||
93 | let buildIndex 0 = "[" | ||
94 | buildIndex _ = "," | ||
95 | in later buildIndex | ||
96 | |||
97 | fPositional :: Format r (Positional -> r) | ||
98 | fPositional = | ||
99 | let buildPositional (Positional p r) = bprint (quoted fPosition % " .@ " % fRepetition) p r | ||
100 | in later buildPositional | ||
101 | |||
102 | fSegmentGroupFunction :: Format r (GroupCode -> r) | ||
103 | fSegmentGroupFunction = "segmentGroup " % quoted fGroupCode | ||
104 | |||
105 | fRepetition :: Format r (Repetition -> r) | ||
106 | fRepetition = | ||
107 | let buildRepetition (Repetition Mandatory 1) = bprint "once" | ||
108 | buildRepetition (Repetition Optional 1) = bprint "maybeOnce" | ||
109 | buildRepetition (Repetition Mandatory c) = bprint ("repeatedAtLeastOnce" % " " % fCardinality) c | ||
110 | buildRepetition (Repetition Optional c) = bprint ("repeated" % " " % fCardinality) c | ||
111 | in later buildRepetition | ||
112 | |||
113 | fCardinality :: Format r (Cardinality -> r) | ||
114 | fCardinality = mapf getCardinality int | ||
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Messages/Specification.hs b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Specification.hs new file mode 100644 index 0000000..b1e5c2a --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Specification.hs | |||
@@ -0,0 +1,129 @@ | |||
1 | {-# LANGUAGE DerivingStrategies #-} | ||
2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
3 | |||
4 | module Text.Edifact.Scaffolder.Messages.Specification | ||
5 | ( -- * | ||
6 | specificationParser | ||
7 | , messageNameParser | ||
8 | , listSegments | ||
9 | ) where | ||
10 | |||
11 | import Text.Edifact.Scaffolder.Commons | ||
12 | import Text.Edifact.Scaffolder.Messages.Types | ||
13 | |||
14 | import Data.Maybe (mapMaybe) | ||
15 | import Text.Parsec | ||
16 | |||
17 | type Parser = Parsec String GroupTrail | ||
18 | |||
19 | newtype GroupTrail = GroupTrail [GroupCode] | ||
20 | deriving stock Show | ||
21 | deriving newtype (Semigroup, Monoid) | ||
22 | |||
23 | messageNameParser :: Parser MessageCode | ||
24 | messageNameParser = scanUntil [ | ||
25 | manyTill anyChar (string "Message Type : ") >> MessageCode <$> count 6 upper | ||
26 | ] | ||
27 | |||
28 | specificationParser :: Parser [Dependency] | ||
29 | specificationParser = | ||
30 | let scanElements = scan [ segmentInLine segmentElementParser | ||
31 | , groupInLine groupStartElementParser | ||
32 | ] | ||
33 | in interpretDependencies <$> scanElements <?> "Messages specification" | ||
34 | |||
35 | listSegments :: Parser [SegmentCode] | ||
36 | listSegments = mapMaybe (getSegment . getElement) <$> specificationParser | ||
37 | |||
38 | interpretDependencies :: [Element] -> [Dependency] | ||
39 | interpretDependencies = fmap Dependency | ||
40 | |||
41 | groupInLine :: Parser a -> Parser [a] | ||
42 | groupInLine p = single (many (string " ") *> p <* countClosingGroups) | ||
43 | |||
44 | countClosingGroups :: Parser Int | ||
45 | countClosingGroups = | ||
46 | let parser = many1 (char '-') | ||
47 | *> many1 (char '+') | ||
48 | <* many (char '|') | ||
49 | in length <$> parser | ||
50 | |||
51 | closingGroupTrail :: Parser [Element] | ||
52 | closingGroupTrail = | ||
53 | let groupEndParser = GroupEnd <$> popFromTrail | ||
54 | in countClosingGroups >>= flip count groupEndParser | ||
55 | |||
56 | groupStartElementParser :: Parser Element | ||
57 | groupStartElementParser = | ||
58 | let parseStart pos code rep = GroupStart code (Positional pos rep) | ||
59 | in parseStart <$> positionParser | ||
60 | <* many1 (choice [ () <$ try (oneOf "+*#|X "), () <$ try (string "- ") ]) | ||
61 | <*> groupCodeParser | ||
62 | <* many1 (char ' ') | ||
63 | <*> repetitionParser | ||
64 | <?> "GroupElement" | ||
65 | |||
66 | groupCodeParser :: Parser GroupCode | ||
67 | groupCodeParser = | ||
68 | let parser = manyTill (char '-') (try $ string "-- Segment group") | ||
69 | *> many1 (char ' ') | ||
70 | *> many1 digit | ||
71 | <* many1 space | ||
72 | <* many1 (char '-') | ||
73 | group = GroupCode <$> parser | ||
74 | in group >>= appendToTrail <?> "GroupCodeParser" | ||
75 | |||
76 | appendToTrail :: GroupCode -> Parser GroupCode | ||
77 | appendToTrail code = | ||
78 | let append (GroupTrail trail) = GroupTrail (code : trail) | ||
79 | in code <$ modifyState append | ||
80 | |||
81 | popFromTrail :: Parser GroupCode | ||
82 | popFromTrail = do | ||
83 | previous <- getState | ||
84 | case previous of | ||
85 | GroupTrail (current : trail) -> current <$ putState (GroupTrail trail) | ||
86 | GroupTrail [] -> unexpected "GroupEnd, when state is currently clear" | ||
87 | |||
88 | segmentTrail :: Parser [a] | ||
89 | segmentTrail = [] <$ (many1 (char ' ') <* many (char '|')) | ||
90 | |||
91 | segmentInLine :: Parser Element -> Parser [Element] | ||
92 | segmentInLine p = do | ||
93 | segment <- many (string " ") *> p | ||
94 | trail <- choice [ try closingGroupTrail | ||
95 | , try segmentTrail | ||
96 | ] | ||
97 | pure (segment : trail) | ||
98 | |||
99 | repetitionParser :: Parser Repetition | ||
100 | repetitionParser = | ||
101 | Repetition <$> presenceParser | ||
102 | <* many1 (string " ") | ||
103 | <*> cardinalityParser | ||
104 | <?> "Repetition" | ||
105 | |||
106 | positionParser :: Parser Position | ||
107 | positionParser = | ||
108 | fromString <$> many1 digit | ||
109 | <?> "Position" | ||
110 | |||
111 | segmentElementParser :: Parser Element | ||
112 | segmentElementParser = | ||
113 | let parseSegment pos code rep = Segment code (Positional pos rep) | ||
114 | in parseSegment <$> positionParser | ||
115 | <* many1 (oneOf "+*#|-X ") | ||
116 | <*> segmentCodeParser | ||
117 | <* many1 (string " ") | ||
118 | <* stringToPresenceParser | ||
119 | <* many1 (string " ") | ||
120 | <*> repetitionParser | ||
121 | <?> "SegmentElement" | ||
122 | |||
123 | segmentCodeParser :: Parser SegmentCode | ||
124 | segmentCodeParser = | ||
125 | fromString <$> count 3 upper | ||
126 | <?> "SegmentCode" | ||
127 | |||
128 | cardinalityParser :: Parser Cardinality | ||
129 | cardinalityParser = Cardinality . read <$> many1 digit | ||
diff --git a/scaffolder/src/Text/Edifact/Scaffolder/Messages/Types.hs b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Types.hs new file mode 100644 index 0000000..73cc702 --- /dev/null +++ b/scaffolder/src/Text/Edifact/Scaffolder/Messages/Types.hs | |||
@@ -0,0 +1,36 @@ | |||
1 | {-# LANGUAGE DerivingStrategies #-} | ||
2 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
3 | |||
4 | module Text.Edifact.Scaffolder.Messages.Types where | ||
5 | |||
6 | import Text.Edifact.Scaffolder.Commons | ||
7 | |||
8 | import Data.Function (on) | ||
9 | import Data.Ord (comparing) | ||
10 | |||
11 | newtype Dependency = Dependency { getElement :: Element } deriving newtype (Show, Ord, Eq) | ||
12 | |||
13 | data Repetition = Repetition Presence Cardinality deriving Show | ||
14 | |||
15 | data Positional = Positional { positionalPosition :: Position | ||
16 | , positionalRepetition :: Repetition | ||
17 | } deriving (Show) | ||
18 | |||
19 | instance Eq Positional where | ||
20 | (==) = (==) `on` positionalPosition | ||
21 | |||
22 | instance Ord Positional where | ||
23 | compare = comparing positionalPosition | ||
24 | |||
25 | data Element = Segment SegmentCode Positional | ||
26 | | GroupStart GroupCode Positional | ||
27 | | GroupEnd GroupCode | ||
28 | deriving (Show, Ord, Eq) | ||
29 | |||
30 | getSegment :: Element -> Maybe SegmentCode | ||
31 | getSegment (Segment code _) = Just code | ||
32 | getSegment _ = Nothing | ||
33 | |||
34 | newtype Cardinality = Cardinality { getCardinality :: Int } | ||
35 | deriving stock (Show) | ||
36 | deriving newtype (Eq, Num) | ||