packages feed

super-user-spark-0.2.0.1: src/Types.hs

{-# LANGUAGE OverloadedStrings #-}
module Types
    (
      module Types
    , module Control.Monad.Except
    , module Control.Monad.IO.Class
    , module Control.Monad.Reader
    , module Control.Monad.State
    , module Control.Monad.Writer
    , module Control.Monad.Trans
    , module Text.Parsec
    ) where

import           Control.Applicative
import           Control.Monad          (mzero)
import           Control.Monad.Except   (ExceptT, runExceptT, throwError)
import           Control.Monad.IO.Class (liftIO)
import           Control.Monad.Reader   (ReaderT, ask, asks, runReaderT)
import           Control.Monad.State    (StateT, get, gets, modify, put,
                                         runStateT)
import           Control.Monad.Trans    (lift)
import           Control.Monad.Writer   (WriterT, runWriterT, tell)
import           System.FilePath.Posix  (takeExtension)

import           Data.Aeson             (FromJSON (..), ToJSON (..), Value (..),
                                         object, (.:), (.=))

import           Data.Binary            (Binary (..), Get)
import qualified Data.Binary            as B
import           Data.ByteString        (ByteString)
import           Data.ByteString.Char8  (pack, unpack)
import           System.Directory       (Permissions (..))
import           Text.Parsec            (ParseError)

import           Constants

---[ Cards ]---
type CardName = String
type Source = FilePath
type Destination = FilePath
type Directory = FilePath

data Card = Card {
        card_name    :: CardName
    ,   card_path    :: FilePath
    ,   card_content :: Declaration
    } deriving (Show, Eq)

---[ Declarations ]---
data DeploymentKind = LinkDeployment
                    | CopyDeployment
    deriving (Show, Eq)

instance Binary DeploymentKind where
    put LinkDeployment = B.put True
    put CopyDeployment = B.put False
    get = do
        b <- B.get :: Get Bool
        return $ if b
        then LinkDeployment
        else CopyDeployment

instance Read DeploymentKind where
    readsPrec _ "link" = [(LinkDeployment,"")]
    readsPrec _ "copy" = [(CopyDeployment,"")]
    readsPrec _ _ = []

instance FromJSON DeploymentKind where
    parseJSON (String "link") = return LinkDeployment
    parseJSON (String "copy") = return CopyDeployment
    parseJSON _ = mzero

instance ToJSON DeploymentKind where
    toJSON LinkDeployment = String "link"
    toJSON CopyDeployment = String "copy"



data Declaration = SparkOff CardReference
                 | Deploy Source Destination (Maybe DeploymentKind)
                 | IntoDir Directory
                 | OutofDir Directory
                 | DeployKindOverride DeploymentKind
                 | Alternatives [Directory]
                 | Block [Declaration]
    deriving (Show, Eq)

---[ Card References ]--

-- Reference a card by name.
data CardNameReference = CardNameReference CardName
    deriving (Show, Eq)

-- Reference a card by the file it's in and therein potentially by a name reference
data CardFileReference = CardFileReference FilePath (Maybe CardNameReference)
    deriving (Show, Eq)

type CompilerCardReference = CardFileReference

type CompiledCardReference = FilePath

data DeployerCardReference = DeployerCardCompiled CompiledCardReference
                           | DeployerCardUncompiled CardFileReference
    deriving (Show, Eq)

type CheckerCardReference = DeployerCardReference

instance Read CardFileReference where
    readsPrec _ fp = case length (words fp) of
                      1 -> [(CardFileReference fp Nothing ,"")]
                      2 -> let [f, c] = words fp
                            in [(CardFileReference f (Just $ CardNameReference c), "")]
                      _ -> []

-- TODO refactor these
instance Read DeployerCardReference where
    readsPrec _ fp = case length (words fp) of
                      0 -> []
                      1 -> if takeExtension fp == ".sus"
                            then [(DeployerCardUncompiled (CardFileReference fp Nothing) ,"")]
                            else [(DeployerCardCompiled fp, "")]
                      2 -> let [f, c] = words fp
                            in [(DeployerCardUncompiled (CardFileReference f (Just $ CardNameReference c)), "")]
                      _ -> []


data CardReference = CardFile CardFileReference
                   | CardName CardNameReference
    deriving (Show, Eq)

---[ Base monad ]---

type Sparker = ExceptT SparkError (ReaderT SparkConfig IO)

---[ Options ]---

type Options = (Dispatch, GlobalOptions)

data GlobalOptions = GlobalOptions {
    opt_lineUp              :: Bool
  , opt_indent              :: Int
  , opt_trailingNewline     :: Bool
  , opt_alwaysQuote         :: Bool
  , opt_compress            :: Bool
  , opt_output              :: Maybe FilePath
  , opt_format              :: CompileFormat
  , opt_kind                :: Maybe DeploymentKind
  , opt_overrride           :: Maybe DeploymentKind
  , opt_thoroughness        :: CheckThoroughness
  , opt_replace_links       :: Bool
  , opt_replace_files       :: Bool
  , opt_replace_directories :: Bool
  , opt_replace             :: Bool
  , opt_debug               :: Bool
  } deriving (Show, Eq)

---[ Instructions ]--
type Instructions = (Dispatch, SparkConfig)

---[ Config ]---

data SparkConfig = Config {
        conf_format_lineUp              :: Bool
    ,   conf_format_indent              :: Int
    ,   conf_format_trailingNewline     :: Bool
    ,   conf_format_alwaysQuote         :: Bool
    ,   conf_format_oneLine             :: Bool
    ,   conf_compile_output             :: Maybe FilePath
    ,   conf_compile_format             :: CompileFormat
    ,   conf_compile_kind               :: Maybe DeploymentKind
    ,   conf_compile_override           :: Maybe DeploymentKind
    ,   conf_check_thoroughness         :: CheckThoroughness
    ,   conf_deploy_replace_links       :: Bool
    ,   conf_deploy_replace_files       :: Bool
    ,   conf_deploy_replace_directories :: Bool
    ,   conf_debug                      :: Bool
    } deriving (Show, Eq)


data CompileFormat = FormatBinary
                   | FormatText
                   | FormatJson
                   | FormatStandalone
    deriving (Show, Eq)

instance Read CompileFormat where
    readsPrec _ "binary"     = [(FormatBinary,"")]
    readsPrec _ "text"       = [(FormatText,"")]
    readsPrec _ "json"       = [(FormatJson,"")]
    readsPrec _ "standalone" = [(FormatStandalone,"")]
    readsPrec _ _ = []

data CheckThoroughness = ThoroughnessName
                       | ThoroughnessChecksum
                       | ThoroughnessContent
    deriving (Show, Eq)

instance Read CheckThoroughness where
    readsPrec _ "name"       = [(ThoroughnessName,"")]
    readsPrec _ "checksum"   = [(ThoroughnessChecksum,"")]
    readsPrec _ "content"    = [(ThoroughnessContent,"")]
    readsPrec _ _ = []


data SparkError = ParseError ParseError
                | CompileError CompileError
                | DeployError DeployError
                | UnpredictedError String
    deriving Show

runSparker :: SparkConfig -> Sparker a -> IO (Either SparkError a)
runSparker conf func = runReaderT (runExceptT func) conf

---[ Dispatching ]---

data Dispatch = DispatchParse FilePath
              | DispatchFormat FilePath
              | DispatchCompile CompilerCardReference
              | DispatchCheck CheckerCardReference
              | DispatchDeploy DeployerCardReference
    deriving (Show, Eq)


---[ Compiling Types ]---
data Deployment = Put {
        deployment_srcs :: [FilePath]
    ,   deployment_dst  :: FilePath
    ,   deployment_kind :: DeploymentKind
    }
    deriving Eq

instance Binary Deployment where
    put depl = do
        B.put $ map pack $ deployment_srcs depl
        B.put $ deployment_kind depl
        B.put $ pack $ deployment_dst depl
    get = do
        bsrcs <- B.get :: Get [ByteString]
        kind <- B.get :: Get DeploymentKind
        dst <- B.get :: Get ByteString
        return $ Put {
                deployment_srcs = map unpack bsrcs
            ,   deployment_kind = kind
            ,   deployment_dst = unpack dst
            }

instance Read Deployment where
    readsPrec _ str = [(Put {
                deployment_srcs = srcs
            ,   deployment_dst = dst
            ,   deployment_kind = kind
            }, "")]
      where
          srcs = (map unquote . reverse . drop 2 . reverse) ws
          kind = case lookup (last $ init ws) [(linkKindSymbol, LinkDeployment), (copyKindSymbol, CopyDeployment)] of
                    Nothing -> error "unrecognised deployment kind symbol"
                    Just k  -> k
          dst = last ws
          ws = words str
          unquote = tail . init

instance Show Deployment where
    show dep = unwords $ srcs ++ [k,dst]
      where
        srcs = map quote $ deployment_srcs dep
        k = case deployment_kind dep of
                LinkDeployment -> linkKindSymbol
                CopyDeployment -> copyKindSymbol
        dst = quote $ deployment_dst dep
        quote = (\s -> "\"" ++ s ++ "\"")

instance FromJSON Deployment where
    parseJSON (Object o) = Put <$> o .: "sources"
                               <*> o .: "destination"
                               <*> o .: "deployment kind"
    parseJSON _ = mzero

instance ToJSON Deployment where
    toJSON depl = object [
                           "sources" .= deployment_srcs depl
                         , "destination" .= deployment_dst depl
                         , "deployment kind" .= deployment_kind depl
                         ]



type CompileError = String

type SparkCompiler = StateT CompilerState (WriterT [Deployment] Sparker)

type CompilerPrefix = [PrefixPart]

data PrefixPart = Literal String
                | Alts [String]
    deriving (Show, Eq)

runSparkCompiler :: CompilerState -> SparkCompiler a -> Sparker ((a,CompilerState), [Deployment])
runSparkCompiler s func = runWriterT (runStateT func s)


data CompilerState = CompilerState {
        state_current_card             :: Card
    ,   state_current_directory        :: FilePath
    ,   state_all_cards                :: [Card]
    ,   state_declarations_left        :: [Declaration]
    ,   state_deployment_kind_override :: Maybe DeploymentKind
    ,   state_into                     :: Directory
    ,   state_outof_prefix             :: CompilerPrefix
    } deriving (Show, Eq)



---[ Deploying Types ]---

type SparkDeployer = StateT DeployerState Sparker
data DeployerState = DeployerState
data DeployError = PreDeployError [String]
                 | DuringDeployError [String]
                 | PostDeployError [String]
    deriving (Show, Eq)

runSparkDeployer :: DeployerState -> SparkDeployer a -> Sparker (a, DeployerState)
runSparkDeployer state func = runStateT func state

data Diagnostics = NonExistent
                 | IsFile Permissions
                 | IsDirectory Permissions
                 | IsLink Permissions
                 | IsPipe
                 | IsSocket
                 | IsCharDevice
                 | IsBlockDevice
    deriving (Show, Eq)

data PreDeployment = Ready FilePath FilePath DeploymentKind
                   | AlreadyDone
                   | Error String
    deriving (Show, Eq)

data ID = Plain String
        | Var String
    deriving (Show, Eq)


---[ Pretty Types ]---

type SparkFormatter = StateT FormatterState (WriterT String Sparker)
data FormatterState = FormatterState {
        state_current_indent        :: Int
    ,   state_longest_src           :: Int
    ,   state_newline_before_deploy :: Bool
    }
    deriving (Show, Eq)

runSparkFormatter :: FormatterState -> SparkFormatter a -> Sparker ((a, FormatterState), String)
runSparkFormatter state func = runWriterT (runStateT func state)