packages feed

hark-0.2: src/Parsers/Depend.hs

{-----------------------------------------------------------------
 
  (c) 2008-2009 Markus Dittrich 
 
  This program is free software; you can redistribute it 
  and/or modify it under the terms of the GNU General Public 
  License Version 3 as published by the Free Software Foundation. 
 
  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 Version 3 for more details.
 
  You should have received a copy of the GNU General Public 
  License along with this program; if not, write to the Free 
  Software Foundation, Inc., 59 Temple Place - Suite 330, 
  Boston, MA 02111-1307, USA.

--------------------------------------------------------------------}


-- | this module parses the depend string of a package and 
--  returns a list of Dependency data structures
module Parsers.Depend ( depend_parser
                      , defaultDepend
                      , get_depend
                      , get_pdepend
                      , get_rdepend
                      , PackageDep(..)
                      , Dependency(..)
                      ) where 


-- basic imports
import qualified Data.ByteString as B(ByteString, append, drop, 
  empty, head, index, init, intercalate, reverse, split, tail, take)
import Prelude
import System.FilePath.Posix((</>))


-- local imports
import Helpers.ByteString(closeBracket, equalSign, exclMark, 
  openBracket, orSymbol, questMark, spaceChar, spaceCharW, tilde)
import Helpers.Common(split_version_BS, VersionToken(..))
import Helpers.FileIO(try_read_file)
-- import Debug.Trace


-- | data structure for holding a dependency of a package;
-- it includes the name, version, qualifier as well as 
-- the use flag that requested it if applicable
data Dependency = Dependency 
  {
    useFlags  :: [B.ByteString]
  , qualifier :: B.ByteString
  , fullName  :: B.ByteString
  , package   :: B.ByteString
  , version   :: B.ByteString
  , slot      :: B.ByteString
  , useDeps   :: B.ByteString
  }



-- | implement Eq typeclass for Dependency so we can
-- compare; for now we define two Dependencies 
-- identical if they both have the identical full
-- name and are pulled in by the same use flag
instance Eq Dependency where

  a == b   =  (fullName a == fullName b) 
              && (useFlags a == useFlags b)



-- | implement Show typeclass for dependency
-- We simply want to display the content in an easy
-- to parse form for now
instance Show Dependency where
  show d = show $ B.intercalate spaceChar line

    where
      part1 = [fullName d, package d, version d, qualifier d]
      line  = part1 ++ (useFlags d)



-- | default Dependency data structure
defaultDepend :: Dependency 
defaultDepend = Dependency 
  {
    useFlags  = []
  , qualifier = B.empty
  , fullName  = B.empty
  , package   = B.empty
  , version   = B.empty
  , slot      = B.empty
  , useDeps   = B.empty
  }



-- | type holding all dependencies for a particular package
newtype PackageDep = PackageDep (String,[Dependency]) 
  deriving(Show)



-- | data type used to implement a stack for storing
-- useflags or || atoms
data StackItem = Use B.ByteString | Or | Bracket
    deriving(Eq,Show)



-- | raw functions for attempting to open DEPEND, RDEPEND,
-- and RDEPEND files

-- | DEPEND
get_depend :: FilePath -> IO (Maybe B.ByteString)
get_depend path = try_read_file ( path </> "DEPEND")



-- | RDPEND
get_rdepend :: FilePath -> IO (Maybe B.ByteString)
get_rdepend path = try_read_file ( path </> "RDEPEND")



-- | PDEPEND
get_pdepend :: FilePath -> IO (Maybe B.ByteString)
get_pdepend path = try_read_file ( path </> "PDEPEND")




-- | top level function for parsing a package dependency string
depend_parser :: B.ByteString -> [Dependency]
depend_parser = parse_recursive . B.split spaceCharW 



-- | recursive parser for depend elements; can go infinitely
-- deep and relies on the fact that each nested level is
-- enclosed in parentheses
parse_recursive :: [B.ByteString] -> [Dependency]
parse_recursive atoms = parse atoms [] [] 
  
  where
    parse :: [B.ByteString] -> [StackItem] -> [Dependency] 
              -> [Dependency]
    parse [] _ deps = deps
    parse (item:items) uFlags deps 

      -- strip the trailing question mark
      | is_useflag item           
          = parse items (Use (B.init item):uFlags) deps

      | is_or item                
          = parse items (Or:uFlags) deps

      -- ignore
      | is_opening_bracket item 
          = parse items (Bracket:uFlags) deps


      | is_closing_bracket item 
          = parse items (pop_bracket uFlags) deps
              

      | otherwise 
          = parse items uFlags ( 
              create_depend (extract_uses uFlags) item 
                : deps )
            

        where
          -- every time we encounter a closing bracket we either pop
          -- a single opening bracket or a single opening bracket plus a 
          -- preceeding Use/Or token if present
          pop_bracket [] = error "Parse failure in a (R/P)DEPEND statement"
          pop_bracket ((Bracket):(Or):xs) = xs 
          pop_bracket ((Bracket):(Use _):xs) = xs
          pop_bracket (_:xs) = xs


          -- we use foldl since we need to reverse the
          -- stack anyways
          extract_uses :: [StackItem] -> [B.ByteString]
          extract_uses = foldl (\acc x -> case x of
                                            Use a -> a:acc
                                            _     -> acc
                               ) []


          --
          -- helper functions to match depend atoms
          --
          is_opening_bracket :: B.ByteString -> Bool
          is_opening_bracket = (==) openBracket

          is_closing_bracket :: B.ByteString -> Bool
          is_closing_bracket = (==) closeBracket

          is_or :: B.ByteString -> Bool
          is_or = (==) orSymbol

          is_useflag :: B.ByteString -> Bool
          is_useflag x  
            = ( B.head $ B.reverse x) == B.head questMark



-- | creates a new dependency data structure 
create_depend :: [B.ByteString] -> B.ByteString -> Dependency
create_depend flags versionString = 
  
  defaultDepend 
    { 
      useFlags  = flags
    , qualifier = aQualifier
    , fullName  = versionString
    , package   = dName versionTok
    , version   = dVersion versionTok
    , slot      = dSlot versionTok
    , useDeps   = dUseDeps versionTok
    }

  where
    (aQualifier, versionTok) = separate_depend versionString



-- | separates a field in a dependency string into its 
-- qualifier if present (=, >=, <=), name, and version
-- info if present
separate_depend :: B.ByteString -> (B.ByteString, VersionToken)
separate_depend x
  -- matches blocks starting with a !
  -- we need to be careful to catch things like
  -- !<= and !< as well
  | B.head x == B.head exclMark  
      = let 
          (tempQual, tmpToken) = separate_depend (B.tail x)
         in
          (B.append exclMark tempQual, tmpToken)
 
  -- matches tilded deps
  | B.head x == B.head tilde     
      = (tilde, split_version_BS (B.tail x))

  -- matches => and =
  | B.head x == B.head equalSign 
      = (equalSign, split_version_BS (B.tail x))

  -- matches <=
  | B.index x 1 == B.head equalSign  
      = (B.take 2 x, split_version_BS nameElem)

  -- matches plain deps without qualifier
  | otherwise  
      = (B.empty, split_version_BS x) 

  where
    nameElem  = B.drop 2 x