module ExRender (exDisp, exDispQ, exRender) where
import Data.Maybe
import Data.List
import Data.Function
import qualified Data.Map as M
import Text.PrettyPrint
import Control.Arrow ((&&&))
import Distribution.Text
import Distribution.Package
import Distribution.Version
import Distribution.License
import Distribution.Compiler
import Distribution.System
import Distribution.PackageDescription
import Documentation.Haddock.Parser
import Documentation.Haddock.Types hiding (Version)
exWrapWidth ∷ Int
exWrapWidth = 80
exGHCVersion ∷ Version
exGHCVersion = case buildCompilerId of
(CompilerId GHC ver) → ver
x → error $ "Unsupported compiler " ++ show x
exKnownLicenses ∷ [String]
exKnownLicenses = ["CC0", "AGPL-3"]
dquoted ∷ String → String
dquoted [] = []
dquoted ('\\':xs) = "\\\\" ++ dquoted xs
dquoted ('"':xs) = "\\\"" ++ dquoted xs
dquoted ('`':xs) = "\\`" ++ dquoted xs
dquoted ('$':xs) = "\\$" ++ dquoted xs
dquoted (x:xs) = x : dquoted xs
softWidth ∷ Int → [String] → [[String]]
softWidth width = build 0 [] where
build _ ys [] = [reverse ys]
build 0 [] (w:ws) = build (length w) [w] ws
build n ys (w:ws) | n' > width = reverse ys : build 0 [] (w:ws)
| otherwise = build n' (w : ys) ws
where
n' = length w + n
reflow ∷ Int → String → Doc
reflow width = vcat . map (text . unwords) . softWidth width . words
spaces ∷ Doc → Doc
spaces doc | isEmpty doc = empty
| otherwise = space <> doc <> space
nbrackets ∷ Doc → Doc
nbrackets doc | isEmpty doc = empty
| otherwise = brackets doc
class ExRender a where
exDisp :: a → Doc
class ExRenderQ a where
exDispQ :: a → Doc
instance ExRenderQ String where
exDispQ = text . dquoted
instance ExRender Identifier where exDisp (_, s, _) = text s
instance ExRenderQ Identifier where exDispQ = exDisp
instance ExRenderQ id => ExRenderQ (DocH mod id) where
exDispQ x = case x of
DocEmpty → empty
DocAppend a b → exDispQ a <> exDispQ b
DocString s → exDispQ s
DocParagraph a → exDispQ a
DocIdentifier s → exDispQ s
DocModule s → exDispQ s
DocWarning a → exDispQ a
DocEmphasis a → exDispQ a
DocMonospaced a → exDispQ a
DocBold a → exDispQ a
DocHyperlink (Hyperlink _ (Just s)) → exDispQ s
DocHyperlink (Hyperlink s Nothing) → exDispQ s
DocPic _ → empty
DocAName s → exDispQ s
DocProperty s → exDispQ s
DocExamples _ → empty
_ -> error $ "Unsupported haddock node"
instance ExRender LowerBound where
exDisp (LowerBound v InclusiveBound) = ">=" <> disp v
exDisp (LowerBound v ExclusiveBound) = ">" <> disp v
instance ExRender UpperBound where
exDisp (UpperBound v InclusiveBound) = "<=" <> disp v
exDisp (UpperBound v ExclusiveBound) = "<" <> disp v
exDisp x = error $ "Unsupported UpperBound: " ++ show x
maybeExVersion ∷ VersionInterval → Maybe Doc
maybeExVersion = \case
(LowerBound a InclusiveBound, UpperBound b InclusiveBound)
| a == b → Just $ char '=' <> disp a
(LowerBound (Version [0] []) InclusiveBound, ub) → Just $ exDisp ub
(lb, NoUpperBound) → Just $ exDisp lb
(LowerBound (Version [] _) _, _) → Nothing
(_, UpperBound (Version [] _) _) → Nothing
(LowerBound v@(Version a []) InclusiveBound, UpperBound (Version b []) ExclusiveBound)
| init a == init b && succ (last a) == last b →
Just $ char '=' <> disp v <> char '*'
(LowerBound (Version [_] _) _, _) → Nothing
(LowerBound v@(Version a []) InclusiveBound, UpperBound (Version b []) ExclusiveBound)
| init a' == init b && succ (last a') == last b →
Just $ char '~' <> disp v
where a' = init a
_ → Nothing
exVersions ∷ VersionInterval → [Doc]
exVersions = \case
(maybeExVersion → Just x) → [x]
(lb, UpperBound v InclusiveBound) →
exVersions (lb, UpperBound v ExclusiveBound) ++ [char '=' <> disp v]
(LowerBound va@(Version a _) InclusiveBound, ub@(UpperBound (Version b _) ExclusiveBound))
| init a == init b → do
c ← [init a ++ [i] | i ← [last a .. last b 1]]
return $ char '=' <> disp (Version c []) <> char '*'
| length a < length b →
char '=' <> disp va : exVersions (LowerBound (Version (a ++ [0]) []) InclusiveBound, ub)
_ → []
instance ExRender VersionInterval where
exDisp (LowerBound (Version [0] []) InclusiveBound, NoUpperBound) = empty
exDisp (maybeExVersion → Just exVi) = exVi
exDisp (lb, ub) = exDisp lb <> char '&' <> exDisp ub
instance ExRender VersionRange where
exDisp vr = case asVersionIntervals vr of
[vi] → nbrackets $ exDisp vi
(concatMap exVersions → exVis) | not $ null exVis → nbrackets . hcat $ punctuate (char '|') exVis
_ → error $ "Unsupported version range: " ++ display vr
instance ExRender Dependency where
exDisp (Dependency n vr) = "dev-haskell/" <> disp n <> exDisp vr
instance ExRender License where
exDisp (GPL Nothing) = "Unspecified-GPL"
exDisp (GPL (Just v)) = "GPL-" <> disp v
exDisp (AGPL (Just v)) = "AGPL-" <> disp v
exDisp (LGPL Nothing) = "Unspecified-LGPL"
exDisp (LGPL (Just v)) = "LGPL-" <> disp v
exDisp (Apache Nothing) = "Unspecified-Apache"
exDisp (Apache (Just v)) = "Apache-" <> disp v
exDisp (MPL v) = "MPL-" <> disp v
exDisp BSD2 = "BSD-2"
exDisp BSD3 = "BSD-3"
exDisp BSD4 = "BSD-4"
exDisp ISC = "ISC"
exDisp MIT = "MIT"
exDisp PublicDomain = "public-domain"
exDisp (UnknownLicense "BSD2") = "BSD-2"
exDisp (UnknownLicense "MPL-2") = "MPL-2.0"
exDisp (UnknownLicense x) | x `elem` exKnownLicenses = text x
exDisp x = error $ "Unsupported license: " ++ display x
instance ExRender GenericPackageDescription where
exDisp descr = exheres where
nameSelf = pkgName . package $ packageDescription descr
ignoredPkgIds = map (fromJust . simpleParse) ["base", "ghc", "ghc-prim"]
ignoredDep (Dependency n _) | n `elem` ignoredPkgIds = True
ignoredDep _ = False
ignoredTestDep (Dependency n _) | n == nameSelf = True
ignoredTestDep d = ignoredDep d
ignoredBinDep (Dependency n _) | n == nameSelf = True
ignoredBinDep d = ignoredDep d
exDepFn name deps = vcat [
text ("$(" ++ name) <> " \"",
nest 4 . vcat . map exDisp . mergeSortedDeps $ sortDeps deps,
"\")"]
exLibDeps | null libDeps = empty
| otherwise = exDepFn "haskell_lib_dependencies" libDeps
where
libDeps = filter (not . ignoredDep) (collectLibDeps descr)
exBinDeps | null binDeps = empty
| otherwise = exDepFn "haskell_bin_dependencies" binDeps
where
binDeps = filter (not . ignoredBinDep) (collectBinDeps descr)
exTestDeps = case condTestSuites descr of
[] → empty
_ → exDepFn "haskell_test_dependencies" testDeps where
testDeps = filter (not . ignoredTestDep) (collectTestDeps descr)
exDependencies = vcat [
"DEPENDENCIES=\"",
nest 4 exLibDeps,
nest 4 exTestDeps,
nest 4 exBinDeps,
"\""]
pkgDescr = packageDescription descr
hasLib = isJust $ condLibrary descr
hasBin = not . null $ condExecutables descr
hasMods = maybe False (not . null . exposedModules . condTreeData) . condLibrary $ descr
exRequire = "require hackage" <+> nbrackets exParams
where
exHasLib = if hasLib then empty else "has_lib=false"
exHasBin = if hasBin then "has_bin=true" else empty
exHasOptions | hasMods || not hasLib = empty
| otherwise = hsep [
"has_haddock=false",
"has_hscolour=false",
"has_profile=false"
]
exParams = spaces $ exHasLib <+> exHasBin <+> exHasOptions
exSlot = if hasLib then empty else exField "SLOT" "0"
exheres = vcat [
"# Copyright 2015 Mykola Orliuk <virkony@gmail.com>",
"# Distributed under the terms of the GNU General Public License v2",
"# Generated for " <> disp (package pkgDescr),
"",
exRequire,
"",
exField "SUMMARY" (synopsis pkgDescr),
exFieldDoc "DESCRIPTION" (exDispQ . toRegular . parseString $ description pkgDescr),
exField "HOMEPAGE" (homepage pkgDescr),
"",
exField "LICENCES" (exRender $ license pkgDescr),
exSlot,
exField "PLATFORMS" "~amd64",
"",
exDependencies,
"",
exField "BUGS_TO" "virkony@gmail.com",
""
]
collectDeps ∷ (GenericPackageDescription → [CondTree ConfVar [Dependency] a])
→ GenericPackageDescription → [Dependency]
collectDeps view descr = concatMap build (view descr) where
flags = M.fromList . map (flagName &&& id) $ genPackageFlags descr
eval (Var (Flag k)) = flagDefault . fromJust $ M.lookup k flags
eval (Var (OS Linux)) = True
eval (Var (OS _)) = False
eval (Var (Arch X86_64)) = True
eval (Var (Arch _)) = False
eval (Var (Impl GHC vr)) = exGHCVersion `withinRange` vr
eval (Var (Impl _ _)) = False
eval (Lit f) = f
eval (CNot e) = not (eval e)
eval (COr a b) = eval a || eval b
eval (CAnd a b) = eval a || eval b
build t = condTreeConstraints t ++ concatMap buildOptional (condTreeComponents t)
buildOptional (eval → True, t, _) = build t
buildOptional (_, _, Just t) = build t
buildOptional (_, _, Nothing) = []
collectLibDeps, collectBinDeps, collectTestDeps ∷ GenericPackageDescription → [Dependency]
collectLibDeps = collectDeps (maybeToList . condLibrary)
collectBinDeps = collectDeps (map snd . condExecutables)
collectTestDeps = collectDeps (map snd . condTestSuites)
exRender ∷ ExRender a ⇒ a → String
exRender = render . exDisp
exFieldDoc ∷ String → Doc → Doc
exFieldDoc name value | isEmpty value = empty
| otherwise = vcat [text name <> "=\"", value, char '"']
exField ∷ String → String → Doc
exField _ "" = empty
exField name x | length singleLine < exWrapWidth = text singleLine
| otherwise = exFieldDoc name (reflow exWrapWidth (dquoted x))
where
singleLine = name ++ "=\"" ++ dquoted x ++ "\""
sortDeps ∷ [Dependency] → [Dependency]
sortDeps = sortBy (compare `on` display)
mergeSortedDeps ∷ [Dependency] → [Dependency]
mergeSortedDeps [] = []
mergeSortedDeps [x] = [x]
mergeSortedDeps (x:y:z) = case (x, y) of
(Dependency n v, Dependency n' v') | n == n' →
mergeSortedDeps (Dependency n (intersectVersionRanges v v') : z)
_ → x : mergeSortedDeps (y:z)