{-# LANGUAGE DefaultSignatures #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Deprecated.Text -- Copyright : Duncan Coutts 2007 -- -- Maintainer : cabal-devel@haskell.org -- Portability : portable -- -- classes. The difference is that it uses a modern pretty printer and parser -- system and the format is not expected to be Haskell concrete syntax but -- rather the external human readable representation used by Cabal. -- module Distribution.Deprecated.Text ( Text(..), defaultStyle, display, flatStyle, simpleParse, stdParse, -- parse utils parsePackageName, ) where import Distribution.Client.Compat.Prelude import Prelude (read) import Distribution.Deprecated.ReadP ((<++)) import qualified Distribution.Deprecated.ReadP as Parse import Distribution.Parsec import Distribution.Pretty import qualified Text.PrettyPrint as Disp import qualified Data.Set as Set import Data.Version (Version (Version)) import qualified Distribution.Compiler as D import qualified Distribution.License as D import qualified Distribution.ModuleName as D import qualified Distribution.Package as D import qualified Distribution.PackageDescription as D import qualified Distribution.Simple.Setup as D import qualified Distribution.System as D import qualified Distribution.Types.PackageVersionConstraint as D import qualified Distribution.Types.SourceRepo as D import qualified Distribution.Types.UnqualComponentName as D import qualified Distribution.Version as D import qualified Distribution.Types.VersionRange.Internal as D import qualified Language.Haskell.Extension as E -- | /Note:/ this class will soon be deprecated. -- It's not yet, so that we are @-Wall@ clean. class Text a where disp :: a -> Disp.Doc default disp :: Pretty a => a -> Disp.Doc disp = pretty parse :: Parse.ReadP r a default parse :: Parsec a => Parse.ReadP r a parse = parsec -- | Pretty-prints with the default style. display :: Text a => a -> String display = Disp.renderStyle defaultStyle . disp simpleParse :: Text a => String -> Maybe a simpleParse str = case [ p | (p, s) <- Parse.readP_to_S parse str , all isSpace s ] of [] -> Nothing (p:_) -> Just p stdParse :: Text ver => (ver -> String -> res) -> Parse.ReadP r res stdParse f = do cs <- Parse.sepBy1 component (Parse.char '-') _ <- Parse.char '-' ver <- parse let name = intercalate "-" cs return $! f ver (lowercase name) where component = do cs <- Parse.munch1 isAlphaNum if all isDigit cs then Parse.pfail else return cs -- each component must contain an alphabetic character, to avoid -- ambiguity in identifiers like foo-1 (the 1 is the version number). lowercase :: String -> String lowercase = map toLower -- ----------------------------------------------------------------------------- -- Instances for types from the base package instance Text Bool where parse = Parse.choice [ (Parse.string "True" Parse.+++ Parse.string "true") >> return True , (Parse.string "False" Parse.+++ Parse.string "false") >> return False ] instance Text Int where parse = fmap negate (Parse.char '-' >> parseNat) Parse.+++ parseNat instance Text a => Text (Identity a) where disp = disp . runIdentity parse = fmap Identity parse -- | Parser for non-negative integers. parseNat :: Parse.ReadP r Int parseNat = read `fmap` Parse.munch1 isDigit -- TODO: eradicateNoParse instance Text Version where disp (Version branch _tags) -- Death to version tags!! = Disp.hcat (Disp.punctuate (Disp.char '.') (map Disp.int branch)) parse = do branch <- Parse.sepBy1 parseNat (Parse.char '.') -- allow but ignore tags: _tags <- Parse.many (Parse.char '-' >> Parse.munch1 isAlphaNum) return (Version branch []) ------------------------------------------------------------------------------- -- Instances ------------------------------------------------------------------------------- instance Text D.Arch where parse = fmap (D.classifyArch D.Strict) ident instance Text D.BuildType where parse = do name <- Parse.munch1 isAlphaNum case name of "Simple" -> return D.Simple "Configure" -> return D.Configure "Custom" -> return D.Custom "Make" -> return D.Make "Default" -> return D.Custom _ -> fail ("unknown build-type: '" ++ name ++ "'") instance Text D.CompilerFlavor where parse = do comp <- Parse.munch1 isAlphaNum when (all isDigit comp) Parse.pfail return (D.classifyCompilerFlavor comp) instance Text D.CompilerId where parse = do flavour <- parse version <- (Parse.char '-' >> parse) Parse.<++ return D.nullVersion return (D.CompilerId flavour version) instance Text D.ComponentId where parse = D.mkComponentId `fmap` Parse.munch1 abi_char where abi_char c = isAlphaNum c || c `elem` "-_." instance Text D.DefUnitId where parse = D.unsafeMkDefUnitId `fmap` parse instance Text D.UnitId where parse = D.mkUnitId <$> Parse.munch1 (\c -> isAlphaNum c || c `elem` "-_.+") instance Text D.Dependency where parse = do name <- parse Parse.skipSpaces libs <- Parse.option [D.LMainLibName] $ (Parse.char ':' *>) $ pure <$> parseLib name <|> parseMultipleLibs name Parse.skipSpaces ver <- parse Parse.<++ return D.anyVersion Parse.skipSpaces return $ D.Dependency name ver $ Set.fromList libs where makeLib pn ln | D.unPackageName pn == ln = D.LMainLibName | otherwise = D.LSubLibName $ D.mkUnqualComponentName ln parseLib pn = makeLib pn <$> parsecUnqualComponentName parseMultipleLibs pn = Parse.between (Parse.char '{' *> Parse.skipSpaces) (Parse.skipSpaces <* Parse.char '}') $ parsecCommaList $ parseLib pn instance Text E.Extension where parse = do extension <- Parse.munch1 isAlphaNum return (E.classifyExtension extension) instance Text D.FlagName where -- Note: we don't check that FlagName doesn't have leading dash, -- cabal check will do that. parse = D.mkFlagName . lowercase <$> parse' where parse' = (:) <$> lead <*> rest lead = Parse.satisfy (\c -> isAlphaNum c || c == '_') rest = Parse.munch (\c -> isAlphaNum c || c == '_' || c == '-') instance Text D.HaddockTarget where parse = Parse.choice [ Parse.string "for-hackage" >> return D.ForHackage , Parse.string "for-development" >> return D.ForDevelopment] instance Text E.Language where parse = do lang <- Parse.munch1 isAlphaNum return (E.classifyLanguage lang) instance Text D.License where parse = do name <- Parse.munch1 (\c -> isAlphaNum c && c /= '-') version <- Parse.option Nothing (Parse.char '-' >> fmap Just parse) return $! case (name, version :: Maybe D.Version) of ("GPL", _ ) -> D.GPL version ("LGPL", _ ) -> D.LGPL version ("AGPL", _ ) -> D.AGPL version ("BSD2", Nothing) -> D.BSD2 ("BSD3", Nothing) -> D.BSD3 ("BSD4", Nothing) -> D.BSD4 ("ISC", Nothing) -> D.ISC ("MIT", Nothing) -> D.MIT ("MPL", Just version') -> D.MPL version' ("Apache", _ ) -> D.Apache version ("PublicDomain", Nothing) -> D.PublicDomain ("AllRightsReserved", Nothing) -> D.AllRightsReserved ("OtherLicense", Nothing) -> D.OtherLicense _ -> D.UnknownLicense $ name ++ maybe "" (('-':) . display) version instance Text D.Module where parse = do uid <- parse _ <- Parse.char ':' mod_name <- parse return (D.Module uid mod_name) instance Text D.ModuleName where parse = do ms <- Parse.sepBy1 component (Parse.char '.') return (D.fromComponents ms) where component = do c <- Parse.satisfy isUpper cs <- Parse.munch validModuleChar return (c:cs) instance Text D.OS where parse = fmap (D.classifyOS D.Compat) ident instance Text D.PackageVersionConstraint where parse = do name <- parse Parse.skipSpaces ver <- parse Parse.<++ return D.anyVersion Parse.skipSpaces return (D.PackageVersionConstraint name ver) instance Text D.PkgconfigName where parse = D.mkPkgconfigName <$> Parse.munch1 (\c -> isAlphaNum c || c `elem` "+-._") instance Text D.Platform where -- TODO: there are ambigious platforms like: `arch-word-os` -- which could be parsed as -- * Platform "arch-word" "os" -- * Platform "arch" "word-os" -- We could support that preferring variants 'OtherOS' or 'OtherArch' -- -- For now we split into arch and os parts on the first dash. parse = do arch <- parseDashlessArch _ <- Parse.char '-' os <- parse return (D.Platform arch os) where parseDashlessArch :: Parse.ReadP r D.Arch parseDashlessArch = fmap (D.classifyArch D.Strict) dashlessIdent dashlessIdent :: Parse.ReadP r String dashlessIdent = liftM2 (:) firstChar rest where firstChar = Parse.satisfy isAlpha rest = Parse.munch (\c -> isAlphaNum c || c == '_') instance Text D.RepoKind where parse = fmap D.classifyRepoKind ident instance Text D.RepoType where parse = fmap D.classifyRepoType ident instance Text D.UnqualComponentName where parse = D.mkUnqualComponentName <$> parsePackageName instance Text D.PackageIdentifier where parse = do n <- parse v <- (Parse.char '-' >> parse) <++ return D.nullVersion return (D.PackageIdentifier n v) instance Text D.PackageName where parse = D.mkPackageName <$> parsePackageName instance Text D.Version where parse = do branch <- Parse.sepBy1 parseNat (Parse.char '.') -- allow but ignore tags: _tags <- Parse.many (Parse.char '-' >> Parse.munch1 isAlphaNum) return (D.mkVersion branch) instance Text D.VersionRange where parse = expr where expr = do Parse.skipSpaces t <- term Parse.skipSpaces (do _ <- Parse.string "||" Parse.skipSpaces e <- expr return (D.unionVersionRanges t e) Parse.+++ return t) term = do f <- factor Parse.skipSpaces (do _ <- Parse.string "&&" Parse.skipSpaces t <- term return (D.intersectVersionRanges f t) Parse.+++ return f) factor = Parse.choice $ parens expr : parseAnyVersion : parseNoVersion : parseWildcardRange : map parseRangeOp rangeOps parseAnyVersion = Parse.string "-any" >> return D.anyVersion parseNoVersion = Parse.string "-none" >> return D.noVersion parseWildcardRange = do _ <- Parse.string "==" Parse.skipSpaces branch <- Parse.sepBy1 digits (Parse.char '.') _ <- Parse.char '.' _ <- Parse.char '*' return (D.withinVersion (D.mkVersion branch)) parens p = Parse.between (Parse.char '(' >> Parse.skipSpaces) (Parse.char ')' >> Parse.skipSpaces) (do a <- p Parse.skipSpaces return (D.VersionRangeParens a)) digits = do firstDigit <- Parse.satisfy isDigit if firstDigit == '0' then return 0 else do rest <- Parse.munch isDigit return (read (firstDigit : rest)) -- TODO: eradicateNoParse parseRangeOp (s,f) = Parse.string s >> Parse.skipSpaces >> fmap f parse rangeOps = [ ("<", D.earlierVersion), ("<=", D.orEarlierVersion), (">", D.laterVersion), (">=", D.orLaterVersion), ("^>=", D.majorBoundVersion), ("==", D.thisVersion) ] ------------------------------------------------------------------------------- -- ParseUtils ------------------------------------------------------------------------------- parsePackageName :: Parse.ReadP r String parsePackageName = do ns <- Parse.sepBy1 component (Parse.char '-') return $ intercalate "-" ns where component = do cs <- Parse.munch1 isAlphaNum if all isDigit cs then Parse.pfail else return cs -- each component must contain an alphabetic character, to avoid -- ambiguity in identifiers like foo-1 (the 1 is the version number). ident :: Parse.ReadP r String ident = liftM2 (:) firstChar rest where firstChar = Parse.satisfy isAlpha rest = Parse.munch (\c -> isAlphaNum c || c == '_' || c == '-') validModuleChar :: Char -> Bool validModuleChar c = isAlphaNum c || c == '_' || c == '\'' ------------------------------------------------------------------------------- -- Rest of instances, we don't seem to need ------------------------------------------------------------------------------- -- instance Text D.AbiDependency -- instance Text D.AbiHash -- instance Text D.AbiTa -- instance Text D.BenchmarkType -- instance Text D.ExecutableScope -- instance Text D.ExeDependency -- instance Text D.ExposedModule -- instance Text D.ForeignLibOption -- instance Text D.ForeignLibType -- instance Text D.IncludeRenaming -- instance Text D.KnownExtension -- instance Text D.LegacyExeDependency -- instance Text D.LibVersionInfo -- instance Text D.License -- instance Text D.Mixin -- instance Text D.ModuleReexport -- instance Text D.ModuleRenaming -- instance Text D.MungedPackageName -- instance Text D.OpenModule -- instance Text D.OpenUnitId -- instance Text D.PackageVersionConstraint -- instance Text D.PkgconfigDependency -- instance Text D.TestType