-- | -- -- Copyright: -- This file is part of the package vimeta. It is subject to the -- license terms in the LICENSE file found in the top-level -- directory of this distribution and at: -- -- https://github.com/pjones/vimeta -- -- No part of this package, including this file, may be copied, -- modified, propagated, or distributed except according to the terms -- contained in the LICENSE file. -- -- License: BSD-2-Clause -- -- | Mapping files can be used to map file names to other information. module Vimeta.Core.MappingFile ( Parser, parseMappingFile, ) where import Data.Char (isSpace) import System.Directory (doesFileExist) import System.FilePath (takeExtension) import Text.Parsec hiding ((<|>)) import Vimeta.Core.Vimeta -- | Parser type. type Parser a = ParsecT Text () Identity a -- | Internal token used for parsing. data Token a = Comment | Entry FilePath a -- | Parse a mapping file. parseMappingFile :: (MonadIO m) => -- | File name for the mapping file. FilePath -> -- | Parser for the second column. Parser a -> Vimeta m [(FilePath, a)] parseMappingFile filename p = do contents <- runIO $ readFileText filename case runIdentity $ runParserT (mapping p) () filename contents of Left e -> throwError (show e) Right m -> checkFileMappingOrDie m checkFileMappingOrDie :: (MonadIO m) => [(FilePath, a)] -> Vimeta m [(FilePath, a)] checkFileMappingOrDie xs = do ys <- checkFileMapping xs if null (lefts ys) then return (rights ys) else throwError $ report (lefts ys) where report :: [(FilePath, a)] -> String report fs = "the following files are listed in the mapping file " ++ "but they don't exist: \n" ++ intercalate "\n" (map fst fs) -- | Checks to see that all of the file names mentioned exist. If a -- file doesn't exist the @m4v@ file extension is added to it and the -- existence checking happens again. checkFileMapping :: (MonadIO m) => -- | The mapping. [(FilePath, a)] -> Vimeta m [Either (FilePath, a) (FilePath, a)] checkFileMapping = mapM checkFile where checkFile :: (MonadIO m) => (FilePath, a) -> Vimeta m (Either (FilePath, a) (FilePath, a)) checkFile f@(filename, a) = do let ext = takeExtension filename exists <- runIO (doesFileExist filename) case exists of False | null ext -> checkFile (filename ++ ".m4v", a) | otherwise -> return $ Left f True -> return $ Right f -- | The actual file parser. mapping :: Parser a -> Parser [(FilePath, a)] mapping p = entries <$> manyTill (whitespace <|> comment <|> fileName p) eof where entries :: [Token a] -> [(FilePath, a)] entries = concatMap extract . filter predicate predicate :: Token a -> Bool predicate (Entry _ _) = True predicate Comment = False extract :: Token a -> [(FilePath, a)] extract (Entry f a) = [(f, a)] extract Comment = [] -- | Parse a file name followed by whatever the second column parser -- extracts. fileName :: Parser a -> Parser (Token a) fileName p = do first <- anyChar others <- manyTill anyChar (lookAhead space) a <- spaceWithoutNewline >> p return $ Entry (first : others) a "filename and mapping" -- | Skip whitespace. whitespace :: Parser (Token a) whitespace = skipMany1 space >> return Comment -- | Like whitespace, but doesn't span multiple lines. spaceWithoutNewline :: Parser () spaceWithoutNewline = skipMany1 $ satisfy (\c -> isSpace c && c /= '\n') -- | Skip comments. comment :: Parser (Token a) comment = (char '#' >> manyTill anyChar newline >> return Comment) "comment"