packages feed

ltk-0.16.1.0: src/Text/PrinterParser.hs

{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, ScopedTypeVariables, OverloadedStrings #-}
--
-- | Module for saving and restoring preferences and settings
--

module Text.PrinterParser (

    Printer
,   Parser
,   FieldDescriptionS(..)
,   MkFieldDescriptionS
,   mkFieldS

,   applyFieldParsers
,   boolParser
,   intParser
,   lineParser
,   pairParser
,   identifier
,   emptyParser
,   whiteSpace
,   stringParser
,   readParser
,   colorParser

,   emptyPrinter
,   Pretty(..)
,   prettyPrint
,   maybePP

,   symbol
,   colon

,   writeFields
,   showFields
,   readFields
,   parseFields
,   Color(..)
,   toGdkColor
,   fromGdkColor
,   toGdkRGBA
,   fromGdkRGBA
) where

import Text.ParserCombinators.Parsec.Language
import qualified Text.ParserCombinators.Parsec.Token as P
import Text.ParserCombinators.Parsec hiding(Parser)
import qualified Text.PrettyPrint as PP

import Graphics.UI.Editor.Parameters
import Graphics.UI.Editor.Basics
import Data.Maybe (fromMaybe, listToMaybe)
import Data.Monoid ((<>))
import Data.List (foldl')
import qualified Text.ParserCombinators.Parsec as  P
    ((<?>), CharParser(..), parseFromFile)
import Control.Exception as E (catch, IOException)
import qualified Data.Text as T (pack, unpack)
import Data.Text (Text)
import Control.Applicative ((<$>))
import Numeric (showHex)
import System.IO.Unsafe (unsafePerformIO)
import Data.Word (Word16)
import Control.Monad.IO.Class (MonadIO)
import qualified GI.Gdk.Structs.Color as Gdk (Color(..))
import Data.GI.Base.Constructible (Constructible(..))
import GI.Gdk.Structs.Color
       (getColorBlue, getColorGreen, getColorRed, setColorBlue,
        setColorGreen, setColorRed)
import GI.Gdk
       (setRGBAAlpha, getRGBABlue, getRGBAGreen, getRGBARed, setRGBABlue,
        setRGBAGreen, setRGBARed, newZeroRGBA, RGBA)

type Printer beta       =   beta -> PP.Doc
type Parser beta        =   CharParser () beta

-- ------------------------------------------------------------
-- * Parsing with Parsec
-- ------------------------------------------------------------

data FieldDescriptionS alpha =  FDS {
        parameters      ::  Parameters
    ,   fieldPrinter    ::  alpha -> PP.Doc
    ,   fieldParser     ::  alpha -> CharParser () alpha
    }

type MkFieldDescriptionS alpha beta =
    Parameters ->
    Printer beta ->
    Parser beta ->
    Getter alpha beta ->
    Setter alpha beta ->
    FieldDescriptionS alpha

mkFieldS :: {--Eq beta =>--} MkFieldDescriptionS alpha beta
mkFieldS parameter printer parser getter setter =
    FDS parameter
        (\ dat -> (PP.text (case getParameterPrim paraName parameter of
                                Nothing -> ""
                                Just str -> T.unpack str) PP.<> PP.colon)
                PP.$$ PP.nest 15 (printer (getter dat))
                PP.$$ PP.nest 5 (case getParameterPrim paraSynopsis parameter of
                                    Nothing -> PP.empty
                                    Just str -> PP.text . T.unpack $ "--" <> str))
        (\ dat -> try (do
            symbol (fromMaybe "" (getParameterPrim paraName parameter))
            colon
            val <- parser
            return (setter val dat)))

applyFieldParsers ::  a ->  [a ->  CharParser () a] ->  CharParser () a
applyFieldParsers prefs parseF = do
    eof
    return prefs
    <|> do
    let parsers = map (\a ->  a prefs) parseF
    newprefs <-  choice parsers
    whiteSpace
    applyFieldParsers newprefs parseF
    <?> "field parser"

boolParser ::  CharParser () Bool
boolParser = do
    symbol "True" <|> symbol "true"
    return True
    <|> do
    symbol "False" <|> symbol "false"
    return False
    <?> "bool parser"


readParser ::  Read a =>  CharParser () a
readParser = do
    str <- many (noneOf "\n")
    if null str
        then unexpected "read parser on empty string"
        else
            case maybeRead str of
                Nothing -> unexpected $ "read parser no parse " ++ str
                Just r -> return r
    <?> "read parser"
        where maybeRead = listToMaybe . map fst . filter (null . snd) . reads

pairParser ::  CharParser () alpha ->  CharParser () (alpha,alpha)
pairParser p2 = do
    char '('
    v1 <-  p2
    char ','
    v2 <-  p2
    char ')'
    return (v1,v2)
    <?> "pair parser"

stringParser ::  CharParser () Text
stringParser = do
    char '"'
    str <- many (noneOf "\"")
    char '"'
    return (T.pack str)
    <?> "string parser"

lineParser ::  CharParser () Text
lineParser = do
    str <- many (noneOf "\n")
    return (T.pack str)
    <?> "line parser"


intParser ::  CharParser () Int
intParser = do
    i <-  integer
    return (fromIntegral i)

data Color = Color Word16 Word16 Word16 deriving(Eq, Show)

colorParser :: CharParser () Color
colorParser = do
    string "Color"
    whiteSpace
    r <- integer
    whiteSpace
    g <- integer
    whiteSpace
    b <- integer
    return $ Color (fromIntegral r) (fromIntegral g) (fromIntegral b)

toGdkColor :: MonadIO m => Color -> m Gdk.Color
toGdkColor (Color r g b) = do
    c <- new Gdk.Color []
    setColorRed   c r
    setColorGreen c g
    setColorBlue  c b
    return c

fromGdkColor :: MonadIO m => Gdk.Color -> m Color
fromGdkColor c = do
    r <- getColorRed c
    g <- getColorGreen c
    b <- getColorBlue c
    return $ Color r g b

toGdkRGBA :: MonadIO m => Color -> m RGBA
toGdkRGBA (Color r g b) = do
    c <- newZeroRGBA
    setRGBARed   c (fromIntegral r / 65535)
    setRGBAGreen c (fromIntegral g / 65535)
    setRGBABlue  c (fromIntegral b / 65535)
    setRGBAAlpha c 65535
    return c

fromGdkRGBA :: MonadIO m => RGBA -> m Color
fromGdkRGBA c = do
    r <- getRGBARed c
    g <- getRGBAGreen c
    b <- getRGBABlue c
    return $ Color (round (r * 65535)) (round (g * 65535)) (round (b * 65535))

emptyParser ::  CharParser () ()
emptyParser = pzero

prefsStyle  ::  P.LanguageDef st
prefsStyle  = emptyDef  {
        P.commentStart   = "{-"
    ,   P.commentEnd     = "-}"
    ,   P.commentLine    = "--"
    }

lexer :: P.TokenParser st
lexer = P.makeTokenParser prefsStyle

whiteSpace :: CharParser st ()
whiteSpace = P.whiteSpace lexer

symbol :: Text -> CharParser st Text
symbol = (T.pack <$>) . P.symbol lexer . T.unpack

identifier, colon :: CharParser st Text
identifier = T.pack <$> P.identifier lexer
colon = T.pack <$> P.colon lexer

integer = P.integer lexer

-- ------------------------------------------------------------
-- * Printing
-- ------------------------------------------------------------
-- | pretty-print with the default style and 'defaultMode'.
prettyPrint :: Pretty a => a -> Text
prettyPrint a = T.pack $ PP.renderStyle  PP.style (pretty a)

-- | Things that can be pretty-printed
class Pretty a where
    -- | Pretty-print something in isolation.
    pretty :: a -> PP.Doc
    -- | Pretty-print something in a precedence context.
    prettyPrec :: Int -> a -> PP.Doc
    pretty = prettyPrec 0
    prettyPrec _ = pretty

emptyPrinter ::  () ->  PP.Doc
emptyPrinter _ = PP.empty

maybePP :: (a -> PP.Doc) -> Maybe a -> PP.Doc
maybePP _ Nothing = PP.empty
maybePP pp (Just a) = pp a

instance Pretty Text where
    pretty str = PP.text $ T.unpack str

-- ------------------------------------------------------------
-- * Read and write
-- ------------------------------------------------------------

writeFields :: FilePath -> alpha -> [FieldDescriptionS alpha] -> IO ()
writeFields fpath date dateDesc = writeFile fpath (T.unpack $ showFields date dateDesc)

showFields ::  alpha  -> [FieldDescriptionS alpha] ->  Text
showFields date dateDesc = T.pack . PP.render $
    foldl' (\ doc (FDS _ printer _) ->  doc PP.$+$ printer date) PP.empty dateDesc

readFields :: FilePath -> [FieldDescriptionS alpha] -> alpha -> IO alpha
readFields fn fieldDescrs defaultValue = E.catch (do
    res <- P.parseFromFile (parseFields defaultValue fieldDescrs) fn
    case res of
                Left pe -> error $ "Error reading file " ++ show fn ++ " " ++ show pe
                Right r -> return r)
    (\ (e::IOException) -> error $ "Error reading file " ++ show fn ++ " " ++ show e)

parseFields ::  alpha ->  [FieldDescriptionS alpha] ->  P.CharParser () alpha
parseFields defaultValue descriptions =
    let parsersF = map fieldParser descriptions in
        applyFieldParsers defaultValue parsersF
        P.<?> "prefs parser"