@@ -0,0 +1,229 @@
1+-- | Upload to Stackage and Hackage
2+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3+{-# LANGUAGE NoImplicitPrelude #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE RecordWildCards #-}
6+{-# LANGUAGE TupleSections #-}
7+module Stackage2.Upload
13+ , uploadHackageDistro
16+import Control.Monad.Writer.Strict (execWriter, tell)
17+import Data.Default.Class (Default (..))
18+import Filesystem (isDirectory, isFile)
19+import Network.HTTP.Client
20+import Network.HTTP.Client.MultipartFormData
21+import Stackage2.BuildPlan (BuildPlan)
22+import Stackage2.Prelude
23+import Stackage2.ServerBundle (bpAllPackages)
24+import System.IO.Temp (withSystemTempFile)
26+newtype StackageServer = StackageServer { unStackageServer :: Text }
27+ deriving (Show, Eq, Ord, Hashable, IsString)
28+instance Default StackageServer where
29+ def = "http://www.stackage.org"
31+data UploadBundle = UploadBundle
32+ { ubServer :: StackageServer
33+ , ubContents :: LByteString
34+ , ubAlias :: Maybe Text
35+ , ubNightly :: Maybe Text -- ^ should be GHC version
36+ , ubLTS :: Maybe Text -- ^ e.g. 2.3
37+ , ubAuthToken :: Text
39+instance Default UploadBundle where
42+ , ubContents = mempty
44+ , ubNightly = Nothing
46+ , ubAuthToken = "no-auth-token-provided"
49+newtype SnapshotIdent = SnapshotIdent { unSnapshotIdent :: Text }
50+ deriving (Show, Eq, Ord, Hashable, IsString)
52+uploadBundle :: UploadBundle -> Manager -> IO SnapshotIdent
53+uploadBundle UploadBundle {..} man = do
54+ req1 <- parseUrl $ unpack $ unStackageServer ubServer ++ "/upload"
55+ req2 <- formDataBody formData req1
59+ [ ("Authorization", encodeUtf8 ubAuthToken)
60+ , ("Accept", "application/json")
61+ ] ++ requestHeaders req2
63+ , checkStatus = \_ _ _ -> Nothing
64+ , responseTimeout = Just 300000000
66+ res <- httpLbs req3 man
67+ case lookup "x-stackage-ident" $ responseHeaders res of
68+ Just snapid -> return $ SnapshotIdent $ decodeUtf8 snapid
69+ Nothing -> error $ "An error occurred: " ++ show res
71+ params = mapMaybe (\(x, y) -> (x, ) <$> y)
73+ , ("nightly", ubNightly)
76+ formData = ($ []) $ execWriter $ do
77+ forM_ params $ \(key, value) ->
78+ tell' $ partBS key $ encodeUtf8 value
79+ tell' $ partFileRequestBody "stackage" "stackage"
80+ $ RequestBodyLBS ubContents
84+data UploadDocs = UploadDocs
85+ { udServer :: StackageServer
86+ , udDocs :: FilePath -- ^ may be a directory or a tarball
87+ , udAuthToken :: Text
88+ , udSnapshot :: SnapshotIdent
91+uploadDocs (UploadDocs (StackageServer host) fp0 token ident) man = do
94+ then uploadDocsFile $ fpToString fp0
96+ de <- isDirectory fp0
99+ else error $ "Path not found: " ++ fpToString fp0
101+ uploadDocsDir = withSystemTempFile "haddocks.tar.xz" $ \fp h -> do
105+ $ sourceDirectory fp0
106+ $$ filterMC (liftIO . isDirectory)
107+ =$ mapC (fpToString . filename)
109+ writeFile (fp0 </> "index.html") $ mkIndex
110+ (unpack $ unSnapshotIdent ident)
112+ writeFile (fp0 </> "style.css") styleCss
113+ -- FIXME write index.html, style.css
114+ let cp = (proc "tar" $ "cJf" : fp : "index.html" : "style.css" : dirs)
115+ { cwd = Just $ fpToString fp0
117+ withCheckedProcess cp $ \Inherited Inherited Inherited -> return ()
119+ uploadDocsFile fp = do
120+ req1 <- parseUrl $ unpack $ concat
122+ , "/upload-haddock/"
123+ , unSnapshotIdent ident
126+ [ partFileSource "tarball" fp
128+ req2 <- formDataBody formData req1
132+ [ ("Authorization", encodeUtf8 token)
133+ , ("Accept", "application/json")
134+ ] ++ requestHeaders req2
136+ , checkStatus = \_ _ _ -> Nothing
137+ , responseTimeout = Just 300000000
141+uploadHackageDistro :: BuildPlan
142+ -> ByteString -- ^ Hackage username
143+ -> ByteString -- ^ Hackage password
145+ -> IO (Response LByteString)
146+uploadHackageDistro bp username password =
147+ httpLbs (applyBasicAuth username password req)
156+ go (name, version) =
158+ (toBuilder $ display name) ++
160+ (toBuilder $ display version) ++
161+ "\",\"http://www.stackage.org/package/" ++
162+ (toBuilder $ display name) ++
165+ req = "http://hackage.haskell.org/distro/Stackage/packages.csv"
166+ { requestHeaders = [("Content-Type", "text/csv")]
167+ , requestBody = RequestBodyLBS csv
168+ , checkStatus = \_ _ _ -> Nothing
172+mkIndex :: String -> [String] -> String
173+mkIndex snapid dirs = concat
174+ [ "<!DOCTYPE html>\n<html lang='en'><head><title>Haddocks index</title>"
175+ , "<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css'>"
176+ , "<link rel='stylesheet' href='style.css'>"
177+ , "<link rel='shortcut icon' href='http://www.stackage.org/static/img/favicon.ico' />"
179+ , "<body><div class='container'>"
180+ , "<div class='row'><div class='span12 col-md-12'>"
181+ , "<h1>Haddock documentation index</h1>"
182+ , "<p class='return'><a href=\"http://www.stackage.org/stackage/"
184+ , "\">Return to snapshot</a></p><ul>"
185+ , concatMap toLI dirs
186+ , "</ul></div></div></div></body></html>"
199+ [ "@media (min-width: 530px) {"
200+ , "ul { -webkit-column-count: 2; -moz-column-count: 2; column-count: 2 }"
202+ , "@media (min-width: 760px) {"
203+ , "ul { -webkit-column-count: 3; -moz-column-count: 3; column-count: 3 }"
206+ , " margin-left: 0;"
207+ , " padding-left: 0;"
208+ , " list-style-type: none;"
211+ , " background: #f0f0f0;"
212+ , " font-family: 'Lato', sans-serif;"
213+ , " text-shadow: 1px 1px 1px #ffffff;"
214+ , " font-size: 20px;"
215+ , " line-height: 30px;"
216+ , " padding-bottom: 5em;"
219+ , " font-weight: normal;"
220+ , " color: #06537d;"
221+ , " font-size: 45px;"
224+ , " color: #06537d;"
225+ , " font-style: italic;"
228+ , " margin-bottom: 1em;"