Compare commits

...

1 Commits

Author SHA1 Message Date
David Mosbach
ff34ce7713 parser preserves anchors & comments 2023-06-19 02:31:49 +02:00
6 changed files with 515 additions and 130 deletions

View File

@ -6,9 +6,14 @@ module Export where
import Data.Aeson import Data.Aeson
import Data.Map hiding (fromList) import Data.Map hiding (fromList)
import Data.Vector hiding ((!)) import Data.Vector hiding ((!), (++))
import Workflow (NodeData(..), EdgeData(..), GraphData(..), Entry(..)) import Workflow (NodeData(..), EdgeData(..), GraphData(..), Entry(..), Message(..), Label(..), Viewers (..))
import Data.Text (pack) import Data.Text (Text, pack)
-- import Data.YAML (Node (..))
import Data.YAML.Event (tagToText, Pos)
import Data.Maybe (fromMaybe)
import Parser (YAMLNode (..), AnchorData (..))
import Data.Aeson.Types (toJSONKeyText)
--------------------------------------- ---------------------------------------
@ -17,13 +22,56 @@ module Export where
instance ToJSON Entry where instance ToJSON Entry where
toJSON (Single s) = toJSON s toJSON (Single s) = toJSON s
toJSON (Msg m) = toJSON m
toJSON (Dict d) = toJSON d toJSON (Dict d) = toJSON d
toJSON (List l) = toJSON l toJSON (List l) = toJSON l
toJSON (Val v) = toJSON v toJSON (Val v) = toJSON v
instance ToJSON YAMLNode where
toJSON (Scalar b c a p) = object [
"content" .= show b,
"comment" .= c,
"anchor" .= a,
"position" .= p
]
instance ToJSONKey YAMLNode where
toJSONKey = toJSONKeyText display where
display :: YAMLNode -> Text
display (Scalar bytes _ _ _) = pack $ show bytes
instance ToJSON AnchorData where
toJSON (AnchorDef a) = object ["type" .= String "anchor", "name" .= a]
toJSON (AnchorAlias a) = object ["type" .= String "alias", "name" .= a]
toJSON NoAnchor = Null
instance ToJSON Pos
instance ToJSON Message where
toJSON (Message content status viewers comment anchor) = object [
"content" .= content,
"status" .= status,
"viewers" .= viewers,
"comment" .= comment,
"anchor" .= anchor]
instance ToJSON Viewers where
toJSON (Viewers mappings comment anchor) = object [
"viewers" .= mappings,
"comment" .= comment,
"anchor" .= anchor
]
instance ToJSON Label where
toJSON (Label fallback fallbackLang translations comment anchor) = object [
"fallback" .= fallback,
"fallback-lang" .= fallbackLang,
"translations" .= translations,
"comment" .= comment,
"anchor" .= anchor]
instance ToJSON NodeData where instance ToJSON NodeData where
toJSON (NData d) = Array (fromList $ foldrWithKey newObject [] d) where toJSON (NData d) = Array (fromList $ foldrWithKey newObject [] d) where
newObject :: String -> Map String Entry -> [Value] -> [Value] newObject :: Text -> Map Text Entry -> [Value] -> [Value]
newObject ident values result = object [ newObject ident values result = object [
"id" .= ident, "id" .= ident,
"name" .= values ! "name", "name" .= values ! "name",
@ -37,7 +85,7 @@ module Export where
instance ToJSON EdgeData where instance ToJSON EdgeData where
toJSON (EData d) = Array (fromList $ foldrWithKey newObject [] d) where toJSON (EData d) = Array (fromList $ foldrWithKey newObject [] d) where
newObject :: String -> Map String Entry -> [Value] -> [Value] newObject :: Text -> Map Text Entry -> [Value] -> [Value]
newObject ident values result = object [ newObject ident values result = object [
"id" .= ident, "id" .= ident,
"name" .= values ! "name", "name" .= values ! "name",

View File

@ -6,47 +6,49 @@
module Index where module Index where
import Data.Yaml -- import Data.YAML
import Control.Applicative hiding (empty) import Control.Applicative hiding (empty)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Data.Map import Data.Map
import Data.Maybe (fromMaybe, fromJust) import Data.Maybe (fromMaybe, fromJust)
import Data.Text (Text)
import Parser
type Index = Map String Entry type Index = Map Text Entry
data Entry = Entry { data Entry = Entry {
graphFile :: String, graphFile :: Text,
category :: Maybe String, category :: Maybe Text,
defScope :: Maybe String, defScope :: Maybe Text,
defDescription :: Maybe Description, defDescription :: Maybe Description,
instDescription :: Maybe Description, instDescription :: Maybe Description,
instances :: Value instances :: YAMLNode
} deriving (Show, Generic) } deriving Show
instance FromJSON Entry where instance FromYAML' Entry where
parseJSON (Object o) = Entry <$> fromYAML (Mapping mapping _ _ _) = Entry
o .: "graph-file" <*> <$> mapping <| "graph-file"
o .:? "category" <*> <*> mapping <|? "category"
o .:? "definition-scope" <*> <*> mapping <|? "definition-scope"
o .:? "definition-description" <*> <*> mapping <|? "definition-description"
o .:? "instance-description" <*> <*> mapping <|? "instance-description"
o .: "instances" <*> mapping <| "instances"
parseJSON _ = error "Unexpected yaml" -- parseJSON _ = error "Unexpected yaml"
type Title = String type Title = Text
type Content = String type Content = Text
data Description = Description { data Description = Description {
fallbackLang :: Maybe String, fallbackLang :: Maybe Text,
fallback :: (Maybe Title, Maybe Content), fallback :: (Maybe Title, Maybe Content),
translations :: Map String (Maybe Title, Maybe Content) translations :: Map Text (Maybe Title, Maybe Content)
} deriving (Show, Generic) } deriving Show
instance FromJSON Description where instance FromYAML' Description where
parseJSON (Object o) = Description <$> fromYAML (Mapping mapping _ _ _) = Description
o .:? "fallback-lang" <*> <$> mapping <|? "fallback-lang"
o .: "fallback" <*> <*> mapping <| "fallback"
o .: "translations" <*> mapping <| "translations"
english = "en-eu"; english = "en-eu";
@ -59,8 +61,8 @@ module Index where
def = description.fallback def = description.fallback
in findWithDefault def english description.translations in findWithDefault def english description.translations
getEntryByFile :: String -> Index -> Entry getEntryByFile :: Text -> Index -> Entry
getEntryByFile file index = query (elems index) file where getEntryByFile file index = query (elems index) file where
query :: [Entry] -> String -> Entry query :: [Entry] -> Text -> Entry
query [] _ = error $ "No entries left for " ++ file query [] _ = error $ "No entries left for " ++ show file
query (x:xs) file = if x.graphFile == file then x else query xs file query (x:xs) file = if x.graphFile == file then x else query xs file

View File

@ -1,14 +1,16 @@
module Main where module Main (main) where
----------------Imports---------------- ----------------Imports----------------
import Prelude hiding (lookup)
import System.Environment (getArgs) import System.Environment (getArgs)
import System.Directory import System.Directory
import Data.Yaml (ParseException, decodeEither', Value (String, Null))
import Data.Aeson (encode, encodeFile) import Data.Aeson (encode, encodeFile)
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as BS.L
import Workflow (Workflow, buildData) import Workflow (Workflow, buildData)
import Parser (parse, ParseState (..), decodeWithComments1, decodeWithComments1Strict)
import Export import Export
import Data.Maybe (fromJust, isNothing, isJust, fromMaybe) import Data.Maybe (fromJust, isNothing, isJust, fromMaybe)
import Data.Either (isLeft, fromLeft, fromRight) import Data.Either (isLeft, fromLeft, fromRight)
@ -17,6 +19,15 @@ module Main where
import Text.Regex.TDFA ((=~)) import Text.Regex.TDFA ((=~))
import Index (Index, Entry (Entry), getDefDescription, getInstDescription, getEntryByFile) import Index (Index, Entry (Entry), getDefDescription, getInstDescription, getEntryByFile)
import Data.Char (isSpace) import Data.Char (isSpace)
import Data.Text (pack, Text)
import Data.YAML (decode1Strict, Node, Pos, Parser, parseEither)
import Data.YAML.Event hiding (Scalar)
import Control.Monad (forM_)
import Control.Monad.State.Lazy
import Data.Map.Lazy (Map, insert, lookup, empty)
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Data.Text.Lazy (toStrict)
import Debug.Trace (trace)
--------------------------------------- ---------------------------------------
@ -29,12 +40,36 @@ module Main where
main :: IO () main :: IO ()
main = getArgs >>= process >>= finish where main = getArgs >>= process >>= finish where
process :: [String] -> IO Bool process :: [String] -> IO Bool
process [path] = runParser path >> return True
process args@[_, _] = generateJSON args >> return False process args@[_, _] = generateJSON args >> return False
process args@["--all", src, to] = processDirectory src to >> return False process args@["--all", src, to] = processDirectory src to >> return False
process _ = print "Please provide (1) a source and (2) a target file or provide '--all' and (1) a source and (2) a target directory" >> return True process _ = print "Please provide (1) a source and (2) a target file or provide '--all' and (1) a source and (2) a target directory" >> return True
finish :: Bool -> IO () finish :: Bool -> IO ()
finish abort = if abort then return () else print "Done." finish abort = if abort then return () else print "Done."
printEvents :: FilePath -> IO ()
printEvents path = do
input <- BS.L.readFile path
forM_ (take 700 $ parseEvents input) $ \ev -> case ev of
Left _ -> error "Failed to parse"
Right event -> putStrLn (show (eEvent event) ++ " @" ++ show (posLine $ ePos event))
runParser :: FilePath -> IO ()
runParser path = do
input <- BS.L.readFile path
-- let events = parseEvents input
-- let mainEvents = validHead events
-- unless (isJust mainEvents) . error $ "Missing DocumentStart event"
-- let initState = PState [] empty []
-- let (rootNode, state) = runState (parse $ fromJust mainEvents) initState
let decoded = decodeWithComments1 input :: Either (Pos, String) Workflow
print decoded
-- print rootNode where
-- validHead :: EvStream -> Maybe EvStream
-- validHead ((Right (EvPos StreamStart _)):(Right (EvPos (DocumentStart _) _)):es) = Just es
-- validHead _ = Nothing
-- | Imports the YAML document specified in the first command line argument and -- | Imports the YAML document specified in the first command line argument and
@ -44,8 +79,8 @@ module Main where
-- print $ head args -- print $ head args
-- print $ last args -- print $ last args
content <- BS.readFile (head args) content <- BS.readFile (head args)
let decoded = decodeEither' content :: Either ParseException Workflow let decoded = decodeWithComments1Strict content :: Either (Pos, String) Workflow
if isLeft decoded then throw (fromLeft undefined decoded) else do if isLeft decoded then error (show $ fromLeft undefined decoded) else do
let yaml = fromRight undefined decoded let yaml = fromRight undefined decoded
-- let GData (nodeData, edgeData) = buildData yaml -- let GData (nodeData, edgeData) = buildData yaml
-- putStrLn $ "\nNode Data:\n\n" ++ show nodeData -- putStrLn $ "\nNode Data:\n\n" ++ show nodeData
@ -81,25 +116,25 @@ module Main where
in (match, relative, absolute) in (match, relative, absolute)
writeIndex :: Index -> [(String, FilePath)] -> String -> IO () -- content of _index.yaml -> targets -> content for index.json writeIndex :: Index -> [(String, FilePath)] -> String -> IO () -- content of _index.yaml -> targets -> content for index.json
writeIndex index [] content = print index >> writeFile (to ++ "/index.json") ('[':content) writeIndex index [] content = print index >> writeFile (to ++ "/index.json") ('[':content)
writeIndex index (x:xs) content = let entry = findEntry (fst x) index writeIndex index (x:xs) content = let entry = findEntry (pack $ fst x) index
(name1, description1) = getDefDescription entry (name1, description1) = getDefDescription entry
(name2, description2) = getInstDescription entry (name2, description2) = getInstDescription entry
name = if isJust name1 then name1 else name2 name = if isJust name1 then name1 else name2
description = if isJust description1 then description1 else description2 description = if isJust description1 then description1 else description2
url = snd x url = snd x
format = dropWhileEnd isSpace . map (\y -> if y == '\n' then ' ' else y) format = dropWhileEnd isSpace . map (\y -> if y == '\n' then ' ' else y)
newContent = (if null xs then "" else ",\n") ++ "{\n\"name\": \"" ++ format (fromMaybe (snd x) name) newContent = (if null xs then "" else ",\n") ++ "{\n\"name\": \"" ++ format (show $ fromMaybe (pack $ snd x) name)
++ "\",\n\"description\": \"" ++ "\",\n\"description\": \""
++ format (fromMaybe "" description) ++ "\",\n\"url\": \"" ++ url ++ "\"}" ++ format (show $ fromMaybe (pack "") description) ++ "\",\n\"url\": \"" ++ url ++ "\"}"
in writeIndex index xs (newContent ++ content) in writeIndex index xs (newContent ++ content)
decodeIndex :: FilePath -> IO Index decodeIndex :: FilePath -> IO Index
decodeIndex path = do decodeIndex path = do
content <- BS.readFile path content <- BS.readFile path
let decoded = decodeEither' content :: Either ParseException Index let decoded = decodeWithComments1Strict content :: Either (Pos, String) Index
if isLeft decoded if isLeft decoded
then throw (fromLeft undefined decoded) then error $ show (fromLeft undefined decoded)
else return $ fromRight undefined decoded else return $ fromRight undefined decoded
findEntry :: String -> Index -> Entry findEntry :: Text -> Index -> Entry
findEntry file index = getEntryByFile file index findEntry file index = getEntryByFile file index

224
app/Parser.hs Normal file
View File

@ -0,0 +1,224 @@
{-# Language DuplicateRecordFields,
NoFieldSelectors,
OverloadedRecordDot #-}
module Parser where
import Prelude hiding (lookup)
import qualified Prelude as P
import Control.Monad.State.Lazy
import Data.Map.Lazy (Map, insert, lookup, empty, fromList, toList)
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Data.Text.Lazy (toStrict)
import Debug.Trace (trace)
import Data.Maybe (fromJust, isNothing, isJust, fromMaybe)
import Data.Text (pack, Text)
import Data.YAML (decode1Strict, Node, Pos, Parser, parseEither)
import Data.YAML.Event hiding (Scalar)
import qualified Data.YAML.Event as Y
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as BS.L
activateTrace = False
showTrace :: EvPos -> a -> a
showTrace event action = if activateTrace
then trace (show (eEvent event) ++ " @" ++ show (posLine $ ePos event)) action
else action
data ParseState = PState {
rootNodes :: [YAMLNode],
anchors :: Map Text YAMLNode,
comments :: [Comment] -- YAML comment queue for the next node.
}
data AnchorData = NoAnchor | AnchorDef Text | AnchorAlias Text deriving (Show, Eq)
data YAMLNode =
Scalar {
bytes :: BS.ByteString,
{-tag :: Tag,-}
{-style :: Style,-}
comment :: [Comment], -- TODO every node preceded by a scalar preceded by a comment stores said comment
anchorData :: AnchorData,
pos :: Pos
} | Mapping {
content :: [(YAMLNode, YAMLNode)],
comment :: [Comment],
anchorData :: AnchorData,
pos :: Pos
} | Sequence {
children :: [YAMLNode],
comment :: [Comment],
anchorData :: AnchorData,
pos :: Pos
}
type Comment = Text
disp :: Int -> YAMLNode -> String
disp indent (Scalar b c a p) = replicate indent ' ' ++ "Sca: " ++ show b ++ " #" ++ show c ++ " " ++ show a ++ " @" ++ show (posLine p) ++ "\n"
disp indent (Mapping ct cm a p) = replicate indent ' ' ++ "Map:\n" ++ if null cm then disp' (indent + 2) ct else "" ++ " #" ++ show cm ++ " " ++ show a ++ " @" ++ show (posLine p) ++ "\n" where
disp' :: Int -> [(YAMLNode, YAMLNode)] -> String
disp' indent [] = ""
disp' indent ((k,v):xs) = replicate indent ' ' ++ toStr k ++ ":\n" ++ disp (indent + 4) v ++ disp' indent xs
toStr :: YAMLNode -> String
toStr (Scalar b c _ _) = "| " ++ show b ++ if not (null c) then (" #" ++ show c) else ""
disp indent (Sequence ch cm a p) = replicate indent ' ' ++ "Seq:\n" ++ disp'' (indent + 4) ch ++ " #" ++ show cm ++ " " ++ show a ++ " @" ++ show (posLine p) ++ "\n" where
disp'' :: Int -> [YAMLNode] -> String
disp'' indent [] = ""
disp'' indent (x:xs) = replicate indent ' ' ++ toStr' (indent + 4) x ++ disp'' indent xs
toStr' :: Int -> YAMLNode -> String
toStr' _ (Scalar b c _ _) = "- " ++ show b ++ if not (null c) then (" #" ++ show c) else ""
toStr' indent x = "- " ++ disp indent x
instance Show YAMLNode where
show = disp 0
parse :: EvStream -> State ParseState YAMLNode
parse ((Right (EvPos (DocumentEnd _) pos)):_) = get >>= \pState -> return $ if length pState.rootNodes == 1
then head pState.rootNodes
else Sequence pState.rootNodes [] NoAnchor pos
parse es = do
(root, es') <- parseNode es
pState <- get
when (isJust root) . put $ pState {rootNodes = fromJust root : pState.rootNodes}
parse es'
parseComment :: EvStream -> State ParseState EvStream
parseComment ((Right (EvPos (Y.Comment comment) _)):es) = do
pState <- get
put $ pState {comments = comment : pState.comments}
parseComment es
parseComment es = return es
parseNode :: EvStream -> State ParseState (Maybe YAMLNode, EvStream)
parseNode [] = error "Unexpected eof"
parseNode ((Left _):es) = error "Failed to parse"
parseNode es@((Right (EvPos event pos)):es') = do
pState <- get
showTrace (EvPos event pos) $ case event of
Y.Comment _ -> parseComment es >>= parseNode
Y.SequenceStart anchor _ _ -> parseSequence es' anchor [] >>= \(seq, es'') -> return (Just seq, es'')
Y.MappingStart anchor _ _ -> parseMapping es' anchor [] >>= \(map, es'') -> return (Just map, es'')
Y.Scalar anchor _ _ text -> parseScalar anchor text pos >>= \scal -> return (Just scal, es')
Y.Alias anchor -> parseAlias anchor >>= \a -> return (Just a, es')
_ -> return (Nothing, es) -- error $ "Unexpected event: " ++ show event ++ " @" ++ show (posLine pos)
parseSequence :: EvStream -> Maybe Anchor -> [YAMLNode] -> State ParseState (YAMLNode, EvStream)
parseSequence ((Right (EvPos SequenceEnd pos)):es) anchor children = showTrace (EvPos SequenceEnd pos) $ do
pState <- get
let anchorData = maybe NoAnchor AnchorDef anchor
let seq = Sequence (reverse children) [] anchorData pos
let anchors = if isNothing anchor then pState.anchors else insert (fromJust anchor) seq pState.anchors
put $ pState {anchors = anchors}
return (seq, es)
parseSequence es anchor children = do
(child, es') <- parseNode es
case child of
Nothing -> parseSequence es' anchor children
Just c -> parseSequence es' anchor (c : children)
parseMapping :: EvStream -> Maybe Anchor -> [(YAMLNode, YAMLNode)] -> State ParseState (YAMLNode, EvStream)
parseMapping ((Right (EvPos MappingEnd pos)):es) anchor content = showTrace (EvPos MappingEnd pos) $ do
pState <- get
let anchorData = maybe NoAnchor AnchorDef anchor
let map = Mapping (reverse content) [] anchorData pos
let anchors = if isNothing anchor then pState.anchors else insert (fromJust anchor) map pState.anchors
put $ pState {anchors = anchors}
return (map, es)
parseMapping es anchor content = do
(maybeKey, es') <- parseNode es
case maybeKey of
Nothing -> parseMapping es' anchor content
Just key -> do
unless (isScalar key) . error $ "Key not a scalar: " ++ show key
(maybeVal, es'') <- parseNode es'
let val = fromJust maybeVal
let content' = (key {comment = []}, val {comment = key.comment}) : content -- migrate comment to val to preserve it for the workflow data structure
parseMapping es'' anchor content' where
isScalar :: YAMLNode -> Bool
isScalar (Scalar {}) = True
isScalar _ = False
parseScalar :: Maybe Anchor -> Text -> Pos -> State ParseState YAMLNode
parseScalar anchor text pos = do
pState <- get
let comments = pState.comments
let anchorData = maybe NoAnchor AnchorDef anchor
let scal = Scalar (encodeUtf8 text) comments anchorData pos
let anchors = if isNothing anchor then pState.anchors else insert (fromJust anchor) scal pState.anchors
put $ pState {anchors = anchors, comments = []}
return scal
parseAlias :: Anchor -> State ParseState YAMLNode
parseAlias anchor = do
pState <- get
case lookup anchor pState.anchors of
Nothing -> error $ "Anchor '" ++ show anchor ++ "' not defined"
Just node -> return node {anchorData = AnchorAlias anchor}
class FromYAML' a where
fromYAML :: YAMLNode -> Parser a
instance FromYAML' a => FromYAML' (Maybe a) where
fromYAML y@(Scalar bs _ _ _)
| decodeUtf8 bs == pack "null" = pure Nothing
| otherwise = Just <$> fromYAML y
fromYAML y = Just <$> fromYAML y
instance (Ord k, FromYAML' k, FromYAML' v) => FromYAML' (Map k v) where
fromYAML (Mapping c _ _ _) = fromList <$> mapM (\(a,b) -> (,) <$> fromYAML a <*> fromYAML b) c
instance FromYAML' Text where
fromYAML (Scalar bs _ _ _) = pure $ decodeUtf8 bs
instance FromYAML' YAMLNode where
fromYAML = pure
instance FromYAML' v => FromYAML' [v] where
fromYAML (Sequence c _ _ _) = mapM fromYAML c
instance (FromYAML' a, FromYAML' b) => FromYAML' (a,b) where
fromYAML (Sequence [n1, n2] _ _ _) = (,) <$> fromYAML n1
<*> fromYAML n2
decodeWithComments1 :: FromYAML' v => BS.L.ByteString -> Either (Pos, String) v
decodeWithComments1 input = do
let events = parseEvents input
let mainEvents = validHead events
unless (isJust mainEvents) . error $ "Missing DocumentStart event"
let initState = PState [] empty []
let content = evalState (parse $ fromJust mainEvents) initState
parseEither . fromYAML $ content
where
validHead :: EvStream -> Maybe EvStream
validHead ((Right (EvPos StreamStart _)):(Right (EvPos (DocumentStart _) _)):es) = Just es
validHead _ = Nothing
decodeWithComments1Strict :: FromYAML' v => BS.ByteString -> Either (Pos, String) v
decodeWithComments1Strict = decodeWithComments1 . BS.L.fromChunks . (:[])
(<|) :: FromYAML' a => [(YAMLNode, YAMLNode)] -> Text -> Parser a
mapping <| key = maybe (fail $ "key " ++ show key ++ " not found") fromYAML (P.lookup key $ prep mapping) where
prep :: [(YAMLNode, YAMLNode)] -> [(Text, YAMLNode)]
prep mapping = [(decodeUtf8 scalar.bytes, val) | (scalar, val) <- mapping]
(<|?) :: FromYAML' a => [(YAMLNode, YAMLNode)] -> Text -> Parser (Maybe a)
mapping <|? key = maybe (pure Nothing) fromYAML (P.lookup key $ prep mapping) where
prep :: [(YAMLNode, YAMLNode)] -> [(Text, YAMLNode)]
prep mapping = [(decodeUtf8 scalar.bytes, val) | (scalar, val) <- mapping]

View File

@ -8,13 +8,15 @@ module Workflow where
----------------Imports---------------- ----------------Imports----------------
import Data.Yaml import Data.YAML hiding (Scalar, Mapping, Sequence)
import Control.Applicative hiding (empty) import Control.Applicative hiding (empty)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import Data.Map import Data.Map
import Data.Maybe (fromMaybe, isNothing, fromJust) import Data.Maybe (fromMaybe, isNothing, fromJust)
import Data.Text (pack) import Data.Text (Text, pack)
import Parser
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
--------------------------------------- ---------------------------------------
@ -22,96 +24,165 @@ module Workflow where
-- | Outer structure of a workflow, i.e. nodes and stages. -- | Outer structure of a workflow, i.e. nodes and stages.
data Workflow = Workflow { data Workflow = Workflow {
nodes :: Map String State, nodes :: Map Text State,
stages :: Maybe Value stages :: Maybe YAMLNode,
} deriving (Show, Generic) anchor :: AnchorData
} deriving Show
instance FromJSON Workflow instance FromYAML' Workflow where
fromYAML (Mapping mapping _ anchor pos) = Workflow
<$> mapping <| "nodes"
<*> mapping <|? "stages"
<*> pure anchor
-- | Structure of a node. -- | Structure of a node.
data State = State { data State = State {
viewers :: Maybe StateViewers, viewers :: Maybe StateViewers,
payload :: Maybe (Map String Value), payload :: Maybe (Map Text YAMLNode),
final :: Maybe Final, final :: Maybe Final,
edges :: Maybe (Map String Action), edges :: Maybe (Map Text Action),
messages :: Maybe [Value] messages :: Maybe [Message],
} deriving (Show, Generic) comment :: [Comment],
anchor :: AnchorData
} deriving Show
instance FromJSON State where instance FromYAML' State where
parseJSON (Object o) = State <$> fromYAML (Mapping mapping comment anchor _) = State
o .:? "viewers" <*> <$> mapping <|? "viewers"
o .:? "payload-view" <*> <*> mapping <|? "payload-view"
o .:? "final" <*> <*> mapping <|? "final"
o .:? "edges" <*> <*> mapping <|? "edges"
o .:? "messages" <*> mapping <|? "messages"
parseJSON _ = error "unexpected state data format" <*> pure comment
<*> pure anchor
-- | Wrapper for the `final` value of any node. -- | Wrapper for the `final` value of any node.
newtype Final = Final {final :: String} deriving (Show, Generic) data Final = Final {
final :: String,
comment :: [Comment],
anchor :: AnchorData
} deriving Show
instance FromJSON Final where instance FromYAML' Final where
parseJSON v = case v of fromYAML (Scalar bytes comment anchor _) = Final
String _ -> Final <$> parseJSON v <$> pure (show $ decodeUtf8 bytes)
Bool x -> Final <$> parseJSON (String . pack . show $ x) <*> pure comment
<*> pure anchor
-- case scalar of
-- SStr x -> pure . Final $ show x
-- SBool x -> pure . Final $ show x
-- | Structure of the `viewers` object of any node. -- | Structure of the `viewers` object of any node.
data StateViewers = StateViewers { data StateViewers = StateViewers {
name :: Either Label String, name :: Either Label Text,
viewers :: Maybe [Map String Value] viewers :: Maybe Viewers,
} deriving (Show, Generic) comment :: [Comment],
anchor :: AnchorData
} deriving Show
instance FromJSON StateViewers where instance FromYAML' StateViewers where
parseJSON (Object o) = StateViewers <$> fromYAML (Mapping mapping comment anchor _) = StateViewers
((Left <$> o .: "display-label") <|> (Right <$> o .: "display-label")) <*> <$> ((Left <$> mapping <| "display-label") <|> (Right <$> mapping <| "display-label"))
o .:? "viewers" <*> mapping <|? "viewers"
parseJSON _ = error "unexpected stateViewers data format" <*> pure comment
<*> pure anchor
data Viewers = Viewers {
viewers :: [Map Text YAMLNode],
comment :: [Comment],
anchor :: AnchorData
} deriving Show
instance FromYAML' Viewers where
fromYAML (Sequence seq comment anchor _) = Viewers
<$> pure (Prelude.map (toV empty) seq)
<*> pure comment
<*> pure anchor where
toV :: Map Text YAMLNode -> YAMLNode -> Map Text YAMLNode
toV m (Mapping [] _ _ _) = m
toV m (Mapping ((Scalar b _ _ _,v):xs) c a p) = insert (decodeUtf8 b) v $ toV m (Mapping xs c a p)
-- | Structure of the @display-label@ object of any node or edge. -- | Structure of the @display-label@ object of any node or edge.
data Label = Label { data Label = Label {
fallback :: Maybe String, fallback :: Maybe Text,
translations :: Maybe Value fallbackLang :: Maybe Text,
} deriving (Show, Generic) translations :: Maybe YAMLNode,
comment :: [Comment],
anchor :: AnchorData
} deriving Show
instance FromJSON Label instance FromYAML' Label where
fromYAML (Mapping mapping comment anchor _) = Label
<$> mapping <|? "fallback"
<*> mapping <|? "fallback-lang"
<*> mapping <|? "translations"
<*> pure comment
<*> pure anchor
-- | Structure of an edge. -- | Structure of an edge.
data Action = Action { data Action = Action {
mode :: Maybe String, mode :: Maybe Text,
source :: Maybe String, source :: Maybe Text,
name :: Maybe Label, name :: Maybe Label,
actors :: Maybe [Map String Value], actors :: Maybe Viewers,
viewActor :: Maybe [Map String Value], viewActor :: Maybe Viewers,
viewers :: Maybe [Map String Value], viewers :: Maybe Viewers,
messages :: Maybe [Value], messages :: Maybe [Message],
form :: Maybe Value form :: Maybe YAMLNode,
} deriving (Show, Generic) comment :: [Comment],
anchor :: AnchorData
} deriving Show
instance FromJSON Action where instance FromYAML' Action where
parseJSON (Object o) = Action <$> fromYAML (Mapping mapping comment anchor _) = Action
o .:? "mode" <*> <$> mapping <|? "mode"
o .:? "source" <*> <*> mapping <|? "source"
o .:? "display-label" <*> <*> mapping <|? "display-label"
o .:? "actors" <*> <*> mapping <|? "actors"
o .:? "view-actor" <*> <*> mapping <|? "view-actor"
o .:? "viewers" <*> <*> mapping <|? "viewers"
o .:? "messages" <*> <*> mapping <|? "messages"
o .:? "form" <*> mapping <|? "form"
parseJSON _ = error "unexpected action data format" <*> pure comment
<*> pure anchor
data Message = Message {
content :: Label,
status :: Maybe Text,
viewers :: Maybe Viewers,
comment :: [Comment],
anchor :: AnchorData
} deriving Show
instance FromYAML' Message where
fromYAML (Mapping mapping comment anchor _) = Message
<$> mapping <| "content"
<*> mapping <|? "status"
<*> mapping <|? "viewers"
<*> pure comment
<*> pure anchor
data Entry = Single String | Dict (Map String Value) | List [Entry] | Val Value deriving(Show, Generic) data Entry = Single Text
| Msg Message
| Vie Viewers
| Dict (Map Text YAMLNode)
| List [Entry]
| Val YAMLNode deriving Show
-- | Data of all nodes prepared for JSON encoding. -- | Data of all nodes prepared for JSON encoding.
newtype NodeData = NData (Map String (Map String Entry)) deriving (Show, Generic) newtype NodeData = NData (Map Text (Map Text Entry)) deriving (Show, Generic)
-- | Data of all edges prepared for JSON encoding. -- | Data of all edges prepared for JSON encoding.
newtype EdgeData = EData (Map String (Map String Entry)) deriving (Show, Generic) newtype EdgeData = EData (Map Text (Map Text Entry)) deriving (Show, Generic)
-- | Data of the entire workflow prepared for JSON encoding. -- | Data of the entire workflow prepared for JSON encoding.
newtype GraphData = GData (NodeData , EdgeData) deriving (Show, Generic) newtype GraphData = GData (NodeData , EdgeData) deriving (Show, Generic)
@ -130,53 +201,55 @@ module Workflow where
buildData :: Workflow -> GraphData buildData :: Workflow -> GraphData
buildData wf = GData $ foldrWithKey analyse (NData empty, EData empty) nodes where buildData wf = GData $ foldrWithKey analyse (NData empty, EData empty) nodes where
nodes = insert initID (State {final = Just $ Final "False", nodes = insert initID (State {final = Just $ Final "False" [] NoAnchor,
viewers = Just $ StateViewers (Left (Label (Just initID) Nothing)) Nothing, viewers = Just $ StateViewers (Left (Label (Just initID) Nothing Nothing [] NoAnchor)) Nothing [] NoAnchor,
payload = Nothing, payload = Nothing,
edges = Nothing, edges = Nothing,
messages = Nothing}) wf.nodes messages = Nothing,
analyse :: String -> State -> (NodeData , EdgeData) -> (NodeData , EdgeData) comment = [],
anchor = NoAnchor}) wf.nodes
analyse :: Text -> State -> (NodeData , EdgeData) -> (NodeData , EdgeData)
analyse k s (NData n, ed@(EData e)) = (NData $ insert k (extract s) n, updateEdges k s.edges ed) analyse k s (NData n, ed@(EData e)) = (NData $ insert k (extract s) n, updateEdges k s.edges ed)
extract :: State -> Map String Entry extract :: State -> Map Text Entry
extract s = fromList [("name", Single name), extract s = fromList [("name", Single name),
("viewers", List $ Prelude.map Dict viewers), ("viewers", Vie viewers),
("final", Single final), ("final", Single $ pack final),
("messages", List $ Prelude.map Val messages), ("messages", List $ Prelude.map Msg messages),
("payload", payload)] where ("payload", payload)] where
(name, viewers) = case s.viewers of (name, viewers) = case s.viewers of
Nothing -> ("", [empty :: Map String Value]) Nothing -> ("", Viewers [] [] NoAnchor)
Just x -> case x.name of Just x -> case x.name of
Left y -> (fromMaybe "" y.fallback, fromMaybe [empty :: Map String Value] x.viewers) Left y -> (fromMaybe "" y.fallback, fromMaybe (Viewers [] [] NoAnchor) x.viewers)
Right y -> (y, fromMaybe [empty :: Map String Value] x.viewers) Right y -> (y, fromMaybe (Viewers [] [] NoAnchor) x.viewers)
final = case s.final of final = case s.final of
Nothing -> "" Nothing -> ""
Just x -> x.final Just x -> x.final
messages = fromMaybe [] s.messages messages = fromMaybe [] s.messages
payload = maybe (Val Null) Dict s.payload payload = maybe (Val (Scalar (encodeUtf8 "null") [] NoAnchor (Pos 0 0 0 0))) Dict s.payload
updateEdges :: String -> Maybe (Map String Action) -> EdgeData -> EdgeData updateEdges :: Text -> Maybe (Map Text Action) -> EdgeData -> EdgeData
updateEdges _ Nothing e = e updateEdges _ Nothing e = e
updateEdges targetID (Just edges) (EData e) = EData $ foldrWithKey (\ k action eData -> insert (k ++ "_@_" ++ targetID) (newData k action targetID) eData) e edges updateEdges targetID (Just edges) (EData e) = EData $ foldrWithKey (\ k action eData -> insert (pack $ show k ++ "_@_" ++ show targetID) (newData k action targetID) eData) e edges
newData :: String -> Action -> String -> Map String Entry newData :: Text -> Action -> Text -> Map Text Entry
newData ident a targetID = fromList [("name", Single name), newData ident a targetID = fromList [("name", Single name),
("source", Single source), ("source", Single source),
("target", Single targetID), ("target", Single targetID),
("mode", Single mode), ("mode", Single mode),
("actors", List $ Prelude.map Dict actors), ("actors", Vie actors),
("viewers", List $ Prelude.map Dict viewers), ("viewers", Vie viewers),
("view-actor", List $ Prelude.map Dict viewActor), ("view-actor", Vie viewActor),
("messages", List $ Prelude.map Val messages), ("messages", List $ Prelude.map Msg messages),
("form", Val form)] where ("form", Val form)] where
name = if isNothing a.name name = if isNothing a.name
then ident then ident
else case (fromJust a.name).fallback of else case (fromJust a.name).fallback of
Nothing -> show a.name Nothing -> pack $ show a.name
Just x -> x Just x -> x
source = fromMaybe initID a.source source = fromMaybe initID a.source
mode = fromMaybe "" a.mode mode = fromMaybe "" a.mode
actors = fromMaybe [] a.actors actors = fromMaybe (Viewers [] [] NoAnchor) a.actors
viewers = fromMaybe [] a.viewers viewers = fromMaybe (Viewers [] [] NoAnchor) a.viewers
viewActor = fromMaybe [] a.viewActor viewActor = fromMaybe (Viewers [] [] NoAnchor) a.viewActor
messages = fromMaybe [] a.messages messages = fromMaybe [] a.messages
form = fromMaybe Null a.form form = fromMaybe (Scalar (encodeUtf8 "null") [] NoAnchor (Pos 0 0 0 0)) a.form
--------------------------------------- ---------------------------------------

View File

@ -27,18 +27,21 @@ executable workflow-visualiser
-- Modules included in this executable, other than Main. -- Modules included in this executable, other than Main.
other-modules: Workflow, other-modules: Workflow,
Export, Export,
Index Index,
Parser
-- LANGUAGE extensions used by modules in this package. -- LANGUAGE extensions used by modules in this package.
-- other-extensions: -- other-extensions:
build-depends: base ^>=4.16.3.0, build-depends: base ^>=4.16.3.0,
yaml >= 0.11.11.0, -- yaml >= 0.11.11.0,
aeson >= 2.1.2.0, aeson >= 2.1.2.0,
HsYAML >= 0.2.1.1,
bytestring, bytestring,
containers, containers,
text, text,
vector, vector,
directory, directory,
regex-tdfa regex-tdfa,
mtl
hs-source-dirs: app hs-source-dirs: app
default-language: Haskell2010 default-language: Haskell2010