-- -- >>> Hub.Parse <<< -- -- This module parses the hub XML file to produce a Hub, and the inverse, -- dumping a Hub into XML. -- -- (c) 2011-2015 Chris Dornan module Hub.Parse ( parse , dump , PSt(..) -- kill warnings ) where import Data.Char import Text.Printf import qualified Data.ByteString as B import qualified Text.XML.Expat.Annotated as X import Hub.Poss import Hub.Oops import Hub.Hub import Hub.FilePaths parse :: HubSource -> FilePath -> HubName -> FilePath -> HubKind -> IO Hub parse hs dy hn hf hk = do cts <- B.readFile hf case parse' cts of YUP tr -> case check hs dy hn hf hk tr of NOPE er -> fail_err hn hf er YUP hb -> return hb NOPE er -> fail_err hn hf er dump :: Hub -> IO () dump hub = B.writeFile path xml_bs where xml_bs = B.pack $ map (toEnum.fromEnum) xml xml = unlines $ [ "" , printf " %s" $ string2xml comnt , printf " %s" $ string2xml hcbin , printf " %s" $ string2xml tlbin ] ++ [ printf " %s" $ string2xml civrn | Just civrn<-[mb_civrn] ] ++ [ printf " %s" $ string2xml glbdb ] ++ [ printf " %s" $ string2xml usrgh | Just usrgh<-[mb_usrgh] ] ++ [ printf " %s" $ string2xml usrdb | Just usrdb<-[mb_usrdb] ] ++ [ printf " %s" lks | not $ null lks ] ++ [ printf " %s" $ string2xml $ unwords insa | not $ null insa ] ++ [ "" ] lks = if lk then "rmie" else "" mb_usrgh = fmap glb_hnUHB mb_uh mb_usrdb = fmap usr_dbUHB mb_uh lk = maybe False lockedUHB mb_uh path = path__HUB hub comnt = commntHUB hub hcbin = hc_binHUB hub tlbin = tl_binHUB hub mb_civrn = ci_vrnHUB hub glbdb = glb_dbHUB hub mb_uh = usr___HUB hub insa = inst_aHUB hub fail_err :: HubName -> FilePath -> Err -> IO a fail_err _ hf er = oops PrgO rs where rs = printf "%s:%d:%d %s" hf ln (cn+1) es ln = X.xmlLineNumber lc cn = X.xmlColumnNumber lc X.XMLParseError es lc = er type Loc = X.XMLParseLocation type Err = X.XMLParseError type Tag = String type Node = X.LNode Tag String tx_err :: Loc -> String -> Err tx_err _ = err loc0 err :: Loc -> String -> Err err = flip X.XMLParseError loc0 :: Loc loc0 = X.XMLParseLocation 1 0 0 0 parse' :: B.ByteString -> Poss Err Node parse' = ei2ps . X.parse' X.defaultParseOptions check :: HubSource -> FilePath -> HubName -> FilePath -> HubKind -> Node -> Poss Err Hub check hs dy hn hf hk (X.Element "hub" [] ns lc) = final hs dy hk $ foldl chk (YUP $ start hn hf lc) ns where chk (NOPE er) _ = NOPE er chk (YUP st) nd = foldr (trial st nd) (NOPE $ unrecognised st nd) [ chk_wspce , chk_comnt , chk_hcbin , chk_tlbin , chk_civrn , chk_glbdb , chk_usrdb , chk_usrgh , chk_lockd , chk_insta -- depracated (no warnings yet) , chk_hpbin , chk_cibin ] check _ _ _ _ _ _ = NOPE $ err loc0 "expected simple ..." data PSt = ST { handlST :: HubName , hpathST :: FilePath , locwfST :: Loc , comntST :: Maybe String , hcbinST :: Maybe FilePath , tlbinST :: Maybe FilePath , civrnST :: Maybe FilePath , glbdbST :: Maybe FilePath , usrghST :: Maybe FilePath , usrdbST :: Maybe FilePath , lockdST :: Maybe Bool , instaST :: Maybe [String] } deriving (Show) start :: HubName -> FilePath -> Loc -> PSt start hn fp lc = ST hn fp lc Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing final :: HubSource -> FilePath -> HubKind -> Poss Err PSt -> Poss Err Hub final _ _ _ (NOPE er) = NOPE er final hs dy hk (YUP st) = do co <- get_co hc <- get_hc tl <- get_tl cv <- get_cv gl <- get_gl ia <- get_ia mb_pr <- case (mb_ur,mb_gh) of (Just ur,Nothing) -> (Just . ((,) ur)) `fmap` calc_gh gl (Just ur,Just gh) -> return $ Just (ur,gh) (Nothing,_ ) -> return Nothing return $ HUB hs hn hk hf co hc tl cv gl ia $ fmap mk_uhb mb_pr where get_co = maybe (YUP "" ) YUP mb_co get_hc = maybe (NOPE hc_err ) YUP mb_hc get_tl = maybe (YUP toolsBin) YUP mb_tl get_cv = maybe (YUP Nothing ) (YUP . Just) mb_cv get_gl = maybe (NOPE gl_err ) YUP mb_gl get_ia = maybe (YUP [] ) YUP mb_ia hc_err = err lc "Hub doesn't specify a GHC bin directory" gl_err = err lc "Hub doesn't specify a global package directory" mk_uhb = \(ur,gh) -> UHB dy gh ur $ maybe False id mb_lk calc_gh gl = case match (mk_re globalHubREs) gl of Just gh | isHubName gh == Just GlbHK -> return gh _ -> NOPE $ err loc0 msg where msg = "Could not derive the global hub name from the " ++ "filepath of the global package databse" ST hn hf lc mb_co mb_hc mb_tl mb_cv mb_gl mb_gh mb_ur mb_lk mb_ia = st trial :: PSt -> Node -> (PSt -> Node -> Maybe(Poss Err PSt)) -> Poss Err PSt -> Poss Err PSt trial st nd f ps = maybe ps id $ f st nd unrecognised :: PSt -> Node -> Err unrecognised _ (X.Element tg _ _ lc) = err lc $ printf "<%s> not recognised" tg unrecognised st (X.Text tx ) = err lc $ printf "unexpected text: %s" tx where lc = locwfST st chk_comnt, chk_wspce, chk_hcbin, chk_tlbin, chk_civrn, chk_glbdb, chk_usrgh, chk_usrdb, chk_hpbin, chk_cibin, chk_lockd, chk_insta :: PSt -> Node -> Maybe(Poss Err PSt) chk_wspce st nd = case nd of X.Element _ _ _ _ -> Nothing X.Text txt | all isSpace txt -> Just $ YUP st | otherwise -> Just $ NOPE $ tx_err lc txt_er where lc = locwfST st txt_er = "unexpected top-level text" chk_comnt st0 nd = simple_node True st0 nd "comnt" chk where chk st lc arg = case comntST st of Nothing -> YUP (st{comntST=Just arg}) Just _ -> NOPE $ err lc " respecified" chk_hcbin st0 nd = simple_node False st0 nd "hcbin" chk where chk st lc arg = case hcbinST st of Nothing -> YUP (st{hcbinST=Just arg}) Just _ -> NOPE $ err lc " respecified" chk_tlbin st0 nd = simple_node False st0 nd "tlbin" chk where chk st lc arg = case tlbinST st of Nothing -> YUP (st{tlbinST=Just arg}) Just _ -> NOPE $ err lc " re-specified" chk_civrn st0 nd = simple_node False st0 nd "civrn" chk where chk st lc arg = case civrnST st of Nothing -> YUP (st{civrnST=Just arg}) Just _ -> NOPE $ err lc " re-specified" chk_glbdb st0 nd = simple_node False st0 nd "glbdb" chk where chk st lc arg = case glbdbST st of Nothing -> YUP (st{glbdbST=Just arg}) Just _ -> NOPE $ err lc " respecified" chk_usrgh st0 nd = simple_node False st0 nd "usrgh" chk where chk st lc arg = case usrghST st of Nothing -> YUP (st{usrghST=Just arg}) Just _ -> NOPE $ err lc " respecified" chk_usrdb st0 nd = simple_node False st0 nd "usrdb" chk where chk st lc arg = case usrdbST st of Nothing -> YUP (st{usrdbST=Just arg}) Just _ -> NOPE $ err lc " respecified" chk_lockd st0 nd = simple_node False st0 nd "lockd" chk where chk st lc arg = case lockdST st of Nothing -> YUP (st{lockdST=Just $ not $ all isSpace arg}) Just _ -> NOPE $ err lc " respecified" chk_insta st0 nd = simple_node True st0 nd "insta" chk where chk st lc arg = case instaST st of Nothing -> YUP (st{instaST=Just $ words arg}) Just _ -> NOPE $ err lc " respecified" -- deprecated (pre-0.3) constructions chk_hpbin st0 nd = simple_node False st0 nd "hpbin" $ \st _ _ -> YUP st chk_cibin st0 nd = simple_node False st0 nd "cibin" $ \st _ _ -> YUP st simple_node :: Bool -> PSt -> Node -> Tag -> (PSt->Loc->String->Poss Err PSt) -> Maybe (Poss Err PSt) simple_node ev st (X.Element tg' as ks lc) tg cont | tg==tg' = Just $ do chk_as txt <- chk_ks cont (st {locwfST=lc}) lc txt | otherwise = Nothing where chk_as = case as of [] -> return () _:_ -> NOPE $ err lc ats_er chk_ks = case [ () | X.Element _ _ _ _<-ks ] of [] -> chk_nl _:_ -> NOPE $ err lc txt_er chk_nl = case all_tx of [] | not ev -> NOPE $ err lc emp_er _ -> chk_ls chk_ls = case all (/='\n') all_tx of True -> return all_tx False -> NOPE $ err lc lns_er all_tx = trim $ concat $ [ txt | X.Text txt<-ks ] ats_er = printf "<%s> takes no attributes" tg txt_er = printf "<%s> takes simple text" tg emp_er = printf "<%s> shouldn't be empty" tg lns_er = printf "<%s> should be on a single line" tg simple_node _ _ (X.Text _) _ _ = Nothing string2xml :: String -> String string2xml = concatMap fixChar where fixChar '<' = "<" fixChar '>' = ">" fixChar '&' = "&" fixChar '"' = """ fixChar c | ord c < 0x80 = [c] fixChar c = "&#" ++ show (ord c) ++ ";"