@@ -7,21 +7,31 @@ module Stackage.ServerBundle
7 , epochTime
7 , epochTime
8 , bpAllPackages
8 , bpAllPackages
9 , docsListing
9 , docsListing
11+ , CreateBundleV2 (..)
12import qualified Codec.Archive.Tar as Tar
18import qualified Codec.Archive.Tar as Tar
13import qualified Codec.Archive.Tar.Entry as Tar
19import qualified Codec.Archive.Tar.Entry as Tar
14import qualified Codec.Compression.GZip as GZip
20import qualified Codec.Compression.GZip as GZip
15import qualified Data.Map as M
21import qualified Data.Map as M
22+import Data.Aeson (ToJSON (..), (.=), object, FromJSON (..), (.:), withObject)
23+import System.IO.Temp (withTempDirectory)
16import qualified Data.Yaml as Y
24import qualified Data.Yaml as Y
17−import Filesystem (isFile)
25+import Filesystem (isFile, getWorkingDirectory, listDirectory, isDirectory, canonicalizePath)
18import Foreign.C.Types (CTime (CTime))
26import Foreign.C.Types (CTime (CTime))
19import Stackage.BuildConstraints
27import Stackage.BuildConstraints
20import Stackage.BuildPlan
28import Stackage.BuildPlan
21import Stackage.Prelude
29import Stackage.Prelude
30+import System.IO.Temp (withTempDirectory)
22import qualified System.PosixCompat.Time as PC
31import qualified System.PosixCompat.Time as PC
23import qualified Text.XML as X
32import qualified Text.XML as X
24import Text.XML.Cursor
33import Text.XML.Cursor
34+import System.PosixCompat.Files (createSymbolicLink)
26-- | Get current time
36-- | Get current time
27epochTime :: IO Tar.EpochTime
37epochTime :: IO Tar.EpochTime
@@ -73,13 +83,30 @@ serverBundle time title slug bp@BuildPlan {..} = GZip.compress $ Tar.write
73 map (\(PackageName name) -> name)
83 map (\(PackageName name) -> name)
74 (M.keys $ siCorePackages bpSystemInfo)
84 (M.keys $ siCorePackages bpSystemInfo)
86+-- | Package name is key
87+type DocMap = Map Text PackageDocs
88+data PackageDocs = PackageDocs
90+ , pdModules :: Map Text [Text]
91+ -- ^ module name, path
93+instance ToJSON PackageDocs where
94+ toJSON PackageDocs {..} = object
95+ [ "version" .= pdVersion
96+ , "modules" .= pdModules
98+instance FromJSON PackageDocs where
99+ parseJSON = withObject "PackageDocs" $ \o -> PackageDocs
76docsListing :: BuildPlan
103docsListing :: BuildPlan
77 -> FilePath -- ^ docs directory
104 -> FilePath -- ^ docs directory
78− -> IO ByteString
105+ -> IO DocMap
79docsListing bp docsDir =
106docsListing bp docsDir =
80− fmap (Y.encode . fold) $ mapM go $ mapToList $ bpAllPackages bp
107+ fmap fold $ mapM go $ mapToList $ bpAllPackages bp
82− go :: (PackageName, Version) -> IO (Map Text Y.Value)
109+ go :: (PackageName, Version) -> IO DocMap
83 go (package, version) = do -- handleAny (const $ return mempty) $ do
110 go (package, version) = do -- handleAny (const $ return mempty) $ do
84 let dirname = fpFromText (concat
111 let dirname = fpFromText (concat
85 [ display package
112 [ display package
@@ -107,8 +134,138 @@ docsListing bp docsDir =
107 return $ if e
134 return $ if e
108 then asMap $ singletonMap name [fpToText dirname, href]
135 then asMap $ singletonMap name [fpToText dirname, href]
109 else mempty
136 else mempty
110− return $ singletonMap (display package) $ Y.object
137+ return $ singletonMap (display package) $ PackageDocs
111− [ "version" Y..= display version
138+ { pdVersion = display version
112− , "modules" Y..= m
139+ , pdModules = m
114 else return mempty
141 else return mempty
143+data SnapshotType = STNightly
144+ | STLTS !Int !Int -- ^ major, minor
145+ deriving (Show, Read, Eq, Ord)
147+instance ToJSON SnapshotType where
148+ toJSON STNightly = object
149+ [ "type" .= asText "nightly"
151+ toJSON (STLTS major minor) = object
152+ [ "type" .= asText "lts"
156+instance FromJSON SnapshotType where
157+ parseJSON = withObject "SnapshotType" $ \o -> do
160+ "nightly" -> return STNightly
164+ _ -> fail $ "Unknown type for SnapshotType: " ++ unpack t
166+data CreateBundleV2 = CreateBundleV2
167+ { cb2Plan :: BuildPlan
168+ , cb2Type :: SnapshotType
169+ , cb2DocsDir :: FilePath
170+ , cb2Dest :: FilePath
173+-- | Create a V2 bundle, which contains the build plan, metadata, docs, and doc
175+createBundleV2 :: CreateBundleV2 -> IO ()
176+createBundleV2 CreateBundleV2 {..} = do
177+ docsDir <- canonicalizePath cb2DocsDir
178+ docMap <- docsListing cb2Plan cb2DocsDir
180+ Y.encodeFile (fpToString $ docsDir </> "build-plan.yaml") cb2Plan
181+ Y.encodeFile (fpToString $ docsDir </> "build-type.yaml") cb2Type
182+ Y.encodeFile (fpToString $ docsDir </> "docs-map.yaml") docMap
183+ void $ writeIndexStyle Nothing cb2DocsDir
185+ currentDir <- getWorkingDirectory
186+ files <- listDirectory docsDir
189+ : fpToString (currentDir </> cb2Dest)
191+ : map (fpToString . filename) files
192+ cp = (proc "tar" args) { cwd = Just $ fpToString docsDir }
193+ withCheckedProcess cp $ \ClosedStream Inherited Inherited -> return ()
195+writeIndexStyle :: Maybe Text -- ^ snapshot id
196+ -> FilePath -- ^ docs dir
198+writeIndexStyle msnapid dir = do
201+ $ sourceDirectory dir
202+ $$ filterMC (liftIO . isDirectory)
203+ =$ mapC (fpToString . filename)
205+ writeFile (dir </> "index.html") $ mkIndex
206+ (unpack <$> msnapid)
208+ writeFile (dir </> "style.css") styleCss
211+mkIndex :: Maybe String -> [String] -> String
212+mkIndex msnapid dirs = concat
213+ [ "<!DOCTYPE html>\n<html lang='en'><head><title>Haddocks index</title>"
214+ , "<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css'>"
215+ , "<link rel='stylesheet' href='style.css'>"
216+ , "<link rel='shortcut icon' href='http://www.stackage.org/static/img/favicon.ico' />"
218+ , "<body><div class='container'>"
219+ , "<div class='row'><div class='span12 col-md-12'>"
220+ , "<h1>Haddock documentation index</h1>"
221+ , flip foldMap msnapid $ \snapid -> concat
222+ [ "<p class='return'><a href=\"http://www.stackage.org/stackage/"
224+ , "\">Return to snapshot</a></p>"
227+ , concatMap toLI dirs
228+ , "</ul></div></div></div></body></html>"
241+ [ "@media (min-width: 530px) {"
242+ , "ul { -webkit-column-count: 2; -moz-column-count: 2; column-count: 2 }"
244+ , "@media (min-width: 760px) {"
245+ , "ul { -webkit-column-count: 3; -moz-column-count: 3; column-count: 3 }"
248+ , " margin-left: 0;"
249+ , " padding-left: 0;"
250+ , " list-style-type: none;"
253+ , " background: #f0f0f0;"
254+ , " font-family: 'Lato', sans-serif;"
255+ , " text-shadow: 1px 1px 1px #ffffff;"
256+ , " font-size: 20px;"
257+ , " line-height: 30px;"
258+ , " padding-bottom: 5em;"
261+ , " font-weight: normal;"
262+ , " color: #06537d;"
263+ , " font-size: 45px;"
266+ , " color: #06537d;"
267+ , " font-style: italic;"
270+ , " margin-bottom: 1em;"