{-# 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 Data.Functor.Identity (Identity (..)) 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