packages feed

mcm-0.6.4.10: ParserTypes.hs

-- MCM - Machine Configuration Manager; manages the contents of files and directories
-- Copyright (c) 2013-2016 Anthony Doggett <mcm@interfaces.org.uk>
--
-- Licence:
--     This program is free software: you can redistribute it and/or modify
--     it under the terms of the GNU General Public License as published by
--     the Free Software Foundation, either version 3 of the License, or
--     (at your option) any later version.
--
--     This program is distributed in the hope that it will be useful,
--     but WITHOUT ANY WARRANTY; without even the implied warranty of
--     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--     GNU General Public License for more details.
--
--     You should have received a copy of the GNU General Public License
--     along with this program.  If not, see <http://www.gnu.org/licenses/>.

module ParserTypes (Import(..), PackagePath(..), dummyPP, packagePath, Ident(..), Value(..), UnexpandedDefName(..), DefName(..), Locals(..), OptArgs(..), CondLocal(..), Define(..), Section(..), section, imports, lookupDefine, lookupImport, InvocationCmd(..), ExpandedInvocationCmd(..), InvocationArgs(..), MCMFile(..), Invocation(..), Group(..), Separator(..), Prepend(..), Append(..), ContentLine(..), Content(..), ContentType(..), toContent, VarsExpand(..)) where

import Data.Char (isAlpha)
import Data.List (find, intercalate)
import qualified Data.Map as Map
import qualified Data.Text.Lazy as T
import System.FilePath (joinPath)

-- For translating the full PackagePath into a short name
data Import = Import PackagePath T.Text
    deriving (Show, Eq)

lookupImport :: [Import] -> T.Text -> Maybe PackagePath
lookupImport is i =
    case find (\(Import _ s) -> i==s) is of
        Nothing -> Nothing
        Just (Import pp _) -> Just pp

newtype PackagePath = PackagePath [T.Text]
    deriving (Eq, Ord)

dummyPP :: PackagePath
dummyPP = PackagePath []

instance Show PackagePath where
    show (PackagePath pp) = intercalate "." $ map T.unpack pp

packagePath :: PackagePath -> FilePath
packagePath (PackagePath pp) = joinPath (map T.unpack pp) ++ ".mcm"

newtype Ident = Ident {fromIdent :: T.Text}
    deriving (Eq, Ord)
newtype Value = Value T.Text
    deriving (Eq, Ord)
newtype DefName = DefName {fromDefName :: T.Text}
    deriving (Eq, Ord)
-- UnexpandedDefName is like DefName but can contain one or more @-vars that need expanding
newtype UnexpandedDefName = UnexpandedDefName {fromUnexpandedDefName :: T.Text}
    deriving (Eq, Ord)

instance Show Ident where
    show (Ident i) = T.unpack i
instance Show Value where
    show (Value v) = T.unpack v
instance Show DefName where
    show (DefName d) = T.unpack d
instance Show UnexpandedDefName where
    show (UnexpandedDefName d) = T.unpack d

newtype Locals = Locals {fromLocals :: Map.Map Ident [Content]}
    deriving (Show, Eq)

newtype OptArgs = OptArgs{fromOptArgs :: Map.Map Ident [Content]}
    deriving (Show, Eq)

data CondLocal = CondLocal Ident (Map.Map Value Locals)
    deriving (Show, Eq)

data Define = Define {defName :: DefName
                     ,defArgs :: [Ident]
                     ,defOptargs :: OptArgs
                     ,defLocals :: Locals
                     ,defCondlocals :: [CondLocal]
                     ,defInvokes :: [Invocation]
                     }
    deriving (Show, Eq)

lookupDefine :: Section -> DefName -> Maybe Define
lookupDefine (Section _ _ ds) dname = Map.lookup dname ds

data InvocationCmd = InvFile | InvDir | InvAbsent | InvFragment | InvSymlink
                       | InvLocal UnexpandedDefName | InvImport T.Text UnexpandedDefName
    deriving (Show, Eq, Ord)

data ExpandedInvocationCmd = ExInvFile | ExInvDir | ExInvAbsent | ExInvFragment
        | ExInvSymlink | ExInvLocal DefName | ExInvImport T.Text DefName
    deriving (Show, Eq, Ord)

newtype InvocationArgs = InvocationArgs {fromInvocationArgs :: Map.Map Ident [Content]}
    deriving (Show, Eq)
data Invocation = Invocation InvocationCmd InvocationArgs
    deriving (Show, Eq)

data MCMFile = MCMFile PackagePath Section deriving Show

section :: MCMFile -> Section
section (MCMFile _ s) = s

data Section = Section [Import] (Map.Map Ident [Content]) (Map.Map DefName Define)
    deriving (Show, Eq)

imports :: Section -> [Import]
imports (Section imps _ _) = imps

newtype Group = Group T.Text deriving (Eq, Ord)
newtype Separator = Separator T.Text deriving (Eq, Ord)
newtype Prepend = Prepend T.Text deriving (Eq, Ord)
newtype Append = Append T.Text deriving (Eq, Ord)

instance Show Group where
    show (Group g) = T.unpack g
instance Show Separator where
    show (Separator s) = T.unpack s
instance Show Prepend where
    show (Prepend p) = T.unpack p
instance Show Append where
    show (Append a) = T.unpack a

data ContentLine = Plain T.Text
                 | PrependNewline T.Text

data ContentType = CTSpace | CTDollar

toContent :: ContentType -> ContentLine -> Either String [Content]
toContent ct (PrependNewline s) =
    case toContent ct (Plain s) of
        Left e -> Left e
        Right cs -> Right $ CNewline : cs
toContent _ (Plain s) | T.null s = Right []
toContent ct (Plain s) =
    case ct of
        CTSpace -> return [CString s]
        CTDollar -> case T.span isAlpha s of
            (a, b) | a == T.pack "file"      && T.head b == '(' -> checkEnd (\ss -> Right [CFile ss]) $ T.tail b
            (a, b) | a == T.pack "rawfile"   && T.head b == '(' -> checkEnd (\ss -> Right [CRawFile ss]) $ T.tail b
            (a, b) | a == T.pack "rawstring" && T.head b == '(' -> checkEnd (\ss -> Right [CRawString ss]) $ T.tail b
            (a, b) | a == T.pack "string"    && T.head b == '(' -> checkEnd (\ss -> Right [CExplicitString ss]) $ T.tail b
            (a, b) | a == T.pack "fragments" && T.head b == '(' -> checkEnd makeFragments $ T.tail b
            _ -> Left $ "Expected a valid $COMMAND but got: $" ++ T.unpack s
        where
            checkEnd f ss = if T.last ss == ')'
                            then f $ T.init ss
                            else Left $ "Content missing final ')'? : " ++ T.unpack ss
            makeFragments ss =
                let fargs = T.split (== ',') ss
                    [g, prepend, append, sep] = fargs
                in
                    if length fargs == 4
                        then Right [CFragments (Group g) (Prepend prepend) (Append append) (Separator sep)]
                        else Left $ "Wrong number of fragments arguments: " ++ T.unpack ss

data Content = CString T.Text -- As input
             | CExplicitString T.Text -- As input within explicit string()
             | CRawString T.Text -- String on which to perform no expansion
             | CEmpty -- Nothing
             | CNewline -- A newline
             | CFile T.Text
             | CFragments Group Prepend Append Separator
             | CRawFile T.Text
    deriving (Show, Eq)

data VarsExpand a = VarsExpand {vexpand :: Ident -> T.Text -> Maybe a
                               ,vnoexpand :: T.Text -> a
                               ,vcollapse :: [a] -> a
                               ,vescapeexpand :: T.Text -> T.Text -> a
                               }