Commit 29d74a31 authored by Duncan Coutts's avatar Duncan Coutts
Browse files

Add Upload module direct copy of cabal-upload

parent 55d95fa5
-- This is a quick hack for uploading packages to Hackage.
-- See http://hackage.haskell.org/trac/hackage/wiki/CabalUpload
import Network.Browser
import Network.HTTP
import System.FilePath ((</>))
import Control.Monad
import Data.Char
import Data.Maybe
import Network.URI
import Numeric
import System.Console.GetOpt
import System.Directory
import System.Environment
import System.Exit
import System.IO
import System.Random
type Username = String
type Password = String
uploadURI :: URI
uploadURI = fromJust $ parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/protected/upload-pkg"
checkURI :: URI
checkURI = fromJust $ parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/check-pkg"
main :: IO ()
main = do args <- getArgs
(opts, paths) <- parseOptions args
opts' <- if needsAuth opts then getAuth opts else return opts
mapM_ (handlePackage opts') paths
handlePackage :: Options -> FilePath -> IO ()
handlePackage opts path =
do (uri, auth) <- if optCheck opts
then do output 1 opts $ "Checking " ++ path ++ "... "
return (checkURI, return ())
else do output 1 opts $ "Uploading " ++ path ++ "... "
return (uploadURI,
setAuth uploadURI
(fromJust (optUsername opts))
(fromJust (optPassword opts)))
req <- mkRequest uri path
debug opts $ "\n" ++ show req
(_,resp) <- browse (setErrHandler ignoreMsg
>> setOutHandler ignoreMsg
>> auth
>> request req)
debug opts $ show resp
case rspCode resp of
(2,0,0) -> do outputLn 1 opts "OK"
(x,y,z) -> do outputLn 1 opts "ERROR"
outputLn 0 opts $ "ERROR: " ++ path ++ ": "
++ map intToDigit [x,y,z] ++ " " ++ rspReason resp
outputLn 3 opts $ rspBody resp
needsAuth :: Options -> Bool
needsAuth = not . optCheck
setAuth :: URI -> Username -> Password -> BrowserAction ()
setAuth uri user pwd =
addAuthority $ AuthBasic { auRealm = "Hackage",
auUsername = user,
auPassword = pwd,
auSite = uri }
getAuth :: Options -> IO Options
getAuth opts =
do (mu, mp) <- readAuthFile
u <- case optUsername opts `mplus` mu of
Just u -> return u
Nothing -> promptUsername
p <- case optPassword opts `mplus` mp of
Just p -> return p
Nothing -> promptPassword
return $ opts { optUsername = Just u,
optPassword = Just p }
promptUsername :: IO Username
promptUsername =
do putStr "Hackage username: "
hFlush stdout
getLine
promptPassword :: IO Password
promptPassword =
do putStr "Hackage password: "
hFlush stdout
getLine
authFile :: IO FilePath
authFile = do dir <- getAppUserDataDirectory "cabal-upload"
return $ dir </> "auth"
readAuthFile :: IO (Maybe Username, Maybe Password)
readAuthFile =
do file <- authFile
e <- doesFileExist file
if e then do s <- readFile file
let (u,p) = read s
return (Just u, Just p)
else return (Nothing, Nothing)
ignoreMsg :: String -> IO ()
ignoreMsg _ = return ()
mkRequest :: URI -> FilePath -> IO Request
mkRequest uri path =
do pkg <- readFile path
boundary <- genBoundary
let body = printMultiPart boundary (mkFormData path pkg)
return $ Request {
rqURI = uri,
rqMethod = POST,
rqHeaders = [Header HdrContentType ("multipart/form-data; boundary="++boundary),
Header HdrContentLength (show (length body)),
Header HdrAccept ("text/plain")],
rqBody = body
}
genBoundary :: IO String
genBoundary = do i <- randomRIO (0x10000000000000,0xFFFFFFFFFFFFFF) :: IO Integer
return $ showHex i ""
mkFormData :: FilePath -> String -> [BodyPart]
mkFormData path pkg =
-- yes, web browsers are that stupid (re quoting)
[BodyPart [Header hdrContentDisposition ("form-data; name=package; filename=\""++path++"\""),
Header HdrContentType "application/x-gzip"]
pkg]
hdrContentDisposition :: HeaderName
hdrContentDisposition = HdrCustom "Content-disposition"
-- * Multipart, partly stolen from the cgi package.
data BodyPart = BodyPart [Header] String
printMultiPart :: String -> [BodyPart] -> String
printMultiPart boundary xs =
concatMap (printBodyPart boundary) xs ++ crlf ++ "--" ++ boundary ++ "--" ++ crlf
printBodyPart :: String -> BodyPart -> String
printBodyPart boundary (BodyPart hs c) = crlf ++ "--" ++ boundary ++ crlf ++ concatMap show hs ++ crlf ++ c
crlf :: String
crlf = "\r\n"
-- * Command-line options
data Options = Options {
optUsername :: Maybe Username,
optPassword :: Maybe Password,
optCheck :: Bool,
optVerbosity :: Int
} deriving (Show)
defaultOptions :: Options
defaultOptions = Options {
optUsername = Nothing,
optPassword = Nothing,
optCheck = False,
optVerbosity = 1
}
optDescr :: [OptDescr (Options -> Options)]
optDescr =
[
Option ['c'] ["check"] (NoArg (\o -> o { optCheck = True })) "Don't upload, just check.",
Option ['u'] ["username"] (ReqArg (\u o -> o { optUsername = Just u}) "USERNAME") "Hackage username.",
Option ['p'] ["password"] (ReqArg (\u o -> o { optPassword = Just u}) "PASSWORD") "Hackage password.",
Option "v" ["verbose"] (OptArg (\u o -> o { optVerbosity = maybe 3 read u}) "N") "Control verbosity (N is 0--5, normal verbosity level is 1, -v alone is equivalent to -v3)",
Option ['q'] ["quiet"] (NoArg (\o -> o { optVerbosity = 0 })) "Only essential output. Same as -v 0."
]
parseOptions :: [String] -> IO (Options, [FilePath])
parseOptions args =
do let (fs, files, nonopts, errs) = getOpt' RequireOrder optDescr args
when (not (null errs)) $ die errs
case nonopts of
[] -> return $ (foldl (flip ($)) defaultOptions fs, files)
["--help"] -> usage
_ -> die (map (("unrecognized option "++).show) nonopts)
die :: [String] -> IO a
die errs = do mapM_ (\e -> hPutStrLn stderr $ "cabal-upload: " ++ e) $ errs
hPutStrLn stderr "Try `cabal-upload --help' for more information."
exitFailure
usage :: IO a
usage = do aFile <- authFile
let hdr = unlines ["cabal-upload uploads Cabal source packages to Hackage.",
"",
"You can store your Hackage login in " ++ aFile,
"using the format (\"username\",\"password\").",
"",
"Usage: cabal-upload [OPTION ...] [FILE ...]"]
putStrLn (usageInfo hdr optDescr)
exitWith ExitSuccess
-- * Logging
debug = outputLn 5
output :: Int -> Options -> String -> IO ()
output n opts s = when (optVerbosity opts >= n) $ do hPutStr stderr s
hFlush stderr
outputLn :: Int -> Options -> String -> IO ()
outputLn n opts s = output n opts (s++"\n")
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment