packages feed

PUH-Project-0.1.0.0: Assignments.hs

module Assignments (
    Configuration(Configuration),
    Assignment(Assignment),
    UserIdentifier,
    Year,
    Submission(Submission),
    Type(Homework , Exam , Project),
    getConfiguration,
    listSubmissions,
    getSubmission,
    listFiles,
    createAssignment,
    getSubmissionPath,
    upload) where
import Data.Time.Clock
import Data.Time.Format
import System.Locale
import System.Directory
import Data.List
import qualified Data.Text as T

root = "."

confName = "conf"

-- | A user identifier (not DB id) like a username or JMBAG 
type UserIdentifier = String

-- | Academic year shorthand (e.g. 2015 for 2015/16)
type Year = Integer

-- | An assignment type
data Type = Homework | Exam | Project deriving Eq

instance Show Type where
   show Homework = "homework" 
   show Exam = "exam"
   show Project = "project"

-- | A an assignment configuration data structure
-- | Uses Data.Time.UTCTime
-- | If files is an empty list, ANY number of files are OK
data Configuration = Configuration {
    published    :: UTCTime, -- When to publish
    deadline     :: UTCTime, -- Submission deadline
    lateDeadline :: UTCTime, -- Late submission deadline
    files        :: [String], -- File names to expect
    minScore     :: Double, -- Minimum achievable
    maxScore     :: Double, -- Maximum achievable
    required     :: Double -- Score req to pass
}

instance Show Configuration where
    show c = "published = " ++ show ( published c )++ "\n" ++
             "deadline = " ++ show ( deadline c ) ++ "\n" ++
             "lateDeadline = " ++ show ( lateDeadline c )++ "\n" ++
             "files = " ++ show ( files c) ++ "\n" ++
             "minScore = " ++ show ( minScore c) ++ "\n" ++
             "maxScore = " ++ show ( maxScore c) ++ "\n" ++
             "required = " ++ show ( required c) 

-- | An assignment descriptor
data Assignment = Assignment {
    year   :: Year,
    aType  :: Type,
    number :: Int
} deriving (Show, Eq)

data Submission = Submission {
    assignment     :: Assignment,
    userId         :: UserIdentifier,
    submittedFiles :: [FilePath]
} deriving Show

confFromString str = Configuration {
      published = getUtc $ lv !! 0,
      deadline = getUtc $ lv !! 1,
      lateDeadline = getUtc $ lv !! 2,
      files = read ((lv !! 3) !! 2) :: [String],
      minScore = read ((lv !! 4) !! 2) :: Double,
      maxScore = read ((lv !! 5) !! 2) :: Double,
      required = read ((lv !! 6) !! 2) :: Double
   }
   where lv = map words $ lines str
         getUtc s = read (intercalate " " $ drop 2 s) :: UTCTime

getPathFromAssignment :: Assignment -> FilePath
getPathFromAssignment a = root ++ (show $ year a) ++ "/" ++ (show $ aType a) ++ "/" ++ (show $ number a)

getConfiguration :: Assignment -> IO Configuration
getConfiguration asgn = do 
    fileExist  <- doesFileExist $ (getPathFromAssignment asgn) ++ "/" ++ confName
    if not fileExist
    then error "Configuration file does not exist!"
    else do
        cnf <- readFile ((getPathFromAssignment asgn) ++ "/" ++ confName) 
        return $ confFromString cnf

listSubmissions :: Assignment -> IO [UserIdentifier]
listSubmissions asgn = do
    let path = (getPathFromAssignment asgn)
    check <- doesDirectoryExist path 
    if check
    then do
      contents <- getDirectoryContents path
      let submissions = filter (\x -> x /= confName && x /= "Assignment.pdf" && x /= "." && x /= "..") contents
      return submissions
    else
      error "The assignment does not exist."

getSubmission :: Assignment -> UserIdentifier -> IO Submission
getSubmission asgn user = do
    let path = (getPathFromAssignment asgn) ++ "/" ++ user ++ "/"
    check <- doesDirectoryExist path 
    if check
    then do
      contents <- getDirectoryContents path
      let sFiles = filter (\x -> isInfixOf ".hs" x) contents
      let sbm = Submission { assignment = asgn, userId = user, submittedFiles = sFiles }
      return sbm
    else
      error "The user has not submitted this assignment"

-- | Creates a new assignment from Assignment, configuration and PDF file
-- | The PDF file should be copied, moved or symlinked so that it is
-- | accessible from the assignment directory.
createAssignment :: Assignment -> Configuration -> FilePath -> IO ()
createAssignment asg conf pdf = do 
    let path = getPathFromAssignment asg 
    createDirectoryIfMissing True path 
    copyFile pdf $ path ++ "/Assignment.pdf"
    writeFile (path ++ "/" ++ confName) $ show conf
    
-- Uses Text from Data.Text
-- T.pack   :: String -> Text
-- T.unpack :: Text -> String
upload :: Submission -> T.Text -> String -> IO Submission
upload sub body name = do
    let path = (getPathFromAssignment $ assignment sub) ++ "/" ++ (userId sub)
    createDirectoryIfMissing True path 
    writeFile (path ++ "/" ++ name) (T.unpack body)
    return $ sub {submittedFiles = nub(name:(submittedFiles sub))}
    
listFiles :: Submission -> IO [FilePath]
listFiles s = do
  return $ submittedFiles s
    
getSubmissionPath :: Submission -> FilePath
getSubmissionPath s = getPathFromAssignment (assignment s) ++ "/" ++ (userId s)    
    
-- Test

cnfg = Configuration {
  published = read "2011-11-19 18:28:52.607875 UTC" :: UTCTime,
  deadline = read "2011-11-19 18:28:52.607875 UTC" :: UTCTime,
  lateDeadline = read "2011-11-19 18:28:52.607875 UTC" :: UTCTime,
  files = ["exercises.hs", "homework.hs"],
  minScore = 0.0,
  maxScore = 8.0,
  required = 4.0
}

asgn = Assignment {
  year = 2015,
  aType = Homework,
  number = 1
}

subm = Submission {
  assignment = asgn,
  userId = "Taurius",
  submittedFiles = []
}