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"