module Utils.Katt.Init
(initializeProblem, initializeSession)
where
import Control.Applicative ((<$>), (<*))
import qualified Codec.Archive.Zip as Z
import Control.Arrow ((***))
import Control.Monad (liftM, liftM2, void, when)
import qualified Control.Monad.State as S
import Control.Error hiding (tryIO)
import qualified Control.Exception as E
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as BL
import Data.Foldable (fold)
import Data.List (isSuffixOf, nub)
import Data.Monoid ((<>))
import qualified System.Directory as D
import System.IO (stderr)
import Text.Parsec hiding (token)
import Text.Parsec.ByteString
import qualified Utils.Katt.Configuration as C
import Utils.Katt.Utils
type TestContent = [(B.ByteString, B.ByteString)]
data TestParser
= NoTestsAvailable
| TestAddress B.ByteString
| TestContents TestContent
deriving Show
parseProblemPage :: B.ByteString -> TestParser
parseProblemPage contents =
case res of
Left _ -> NoTestsAvailable
Right test -> test
where res = parse (try parseAddress <|> parseEmbedded) "Test parser" contents
parseAddress :: GenParser Char st TestParser
parseAddress = do
void $ manyTill anyChar (try $ startLink >> lookAhead endParser)
TestAddress . B.cons '/' . B.pack <$> endParser
where
startLink = string "<a href='"
endLink name = string ">" >> string name >> string "</a>"
endParser = manyTill anyChar (char '\'') <* endLink "Download"
parseEmbedded :: GenParser Char st TestParser
parseEmbedded = TestContents <$> tests
where
sp = skipMany $ space <|> newline <|> tab
beginTag tag = void $ char '<' >> sp >> string tag >> sp >> char '>'
endTag tag = void $ char '<' >> sp >> char '/' >> string tag >> sp >> char '>'
htmlTag tag p = do
sp >> beginTag tag >> sp
manyTill p $ try $ endTag tag
tr = htmlTag "tr"
td = htmlTag "td"
pre = htmlTag "pre"
tests = manyTill anyChar (lookAhead startTable) >> endBy1 testTable sp
startTable = void . try $ string "<table class=\"sample\" summary=\"sample data\">"
endTable = void $ string "</table>"
testTable = do
startTable
inner <- tableBody
sp >> endTable
return inner
tableBody = do
void $ tr anyChar
try (fold <$> tr testCase) <* sp
innerTestData = do
sp
B.pack <$> pre anyChar <* sp
testData = liftM fold $ td innerTestData <* sp
testCase = liftM2 (,) testData testData
downloadTestArchive :: B.ByteString -> ConfigEnv IO TestContent
downloadTestArchive url = do
zipFile <- BL.fromChunks . return <$> retrievePublicPage url
zipEntries <- tryIOMsg "Failed to unpack zip file: corrupt archive" $
E.evaluate (Z.zEntries $ Z.toArchive zipFile)
let filterFiles suffix = filter (isSuffixOf suffix . Z.eRelativePath) zipEntries
inFiles = filterFiles inputTestExtension
outFiles = filterFiles outputTestExtension
tryAssert "Failed to unpack zip file: no test files found" $
not (null zipEntries)
tryAssert "Failed to unpack zip file: input and reference count doesn't match" $
length inFiles == length outFiles
return $ zipWith (curry convertEntry) inFiles outFiles
where
convertEntry = getData *** getData
getData = fold . BL.toChunks . Z.fromEntry
retrieveTestFiles :: KattisProblem -> ConfigEnv IO TestContent
retrieveTestFiles problem = do
problemName <- tryIO $retrieveProblemName problem
problemPage <- retrievePublicPage $ problemAddress <> problemName
case parseProblemPage problemPage of
NoTestsAvailable -> do
tryIO $ B.hPutStrLn stderr "No tests available"
return []
TestAddress addr -> downloadTestArchive addr
TestContents list -> return list
sessionPage :: B.ByteString
sessionPage = "/standings/?sid="
parseProblemList :: GenParser Char st [KattisProblem]
parseProblemList = skip >> endBy1 tag skip
where
beginLink = string "<a href='problems/"
skip = manyTill anyChar (void (try beginLink) <|> eof)
tag = do
problem <- manyTill (letter <|> digit) (char '\'')
return . ProblemName $ B.pack problem
initializeSession :: Bool -> ProblemSession -> ConfigEnv IO ()
initializeSession retrieveTests session = do
contents <- retrievePublicPage $ sessionPage <> B.pack (show session)
problems <- nub <$> (EitherT . return . fmapL (B.pack . show) $ parse' contents)
mapM_ (\problem -> do
initializeProblem True retrieveTests problem
restoreDir
)
problems
where
parse' = parse parseProblemList "Problem list parser"
restoreDir = S.liftIO $ D.setCurrentDirectory ".."
initializeProblem :: Bool -> Bool -> KattisProblem -> ConfigEnv IO ()
initializeProblem mkDir retrieveTests problem = do
S.liftIO . putStrLn $ "Initializing problem: " <> show problem
problemName <- tryIO $retrieveProblemName problem
tryIO . when mkDir $ do
D.createDirectoryIfMissing False (B.unpack problemName)
D.setCurrentDirectory (B.unpack problemName)
tryIO $ D.createDirectoryIfMissing False (B.unpack configDir)
fileExists <- S.liftIO C.projectConfigExists
tryAssert
"Project configuration file already exists, please remove it in order to continue."
(not fileExists)
S.modify $ \s -> s { project = Just $ ProblemName problemName }
C.saveProjectConfig
when retrieveTests $ do
tryIO $ D.createDirectory testFolder
files <- zip [1..] <$> retrieveTestFiles problem
mapM_ (\(n :: Integer, (input, output)) -> do
let fileName = testFolder <> "/" <> B.unpack problemName <> "-" <> show n
tryIO $ B.writeFile (fileName <> inputTestExtension) input
tryIO $ B.writeFile (fileName <> outputTestExtension) output)
files