vimeta-0.3.0.0: src/Vimeta/Core/MappingFile.hs
-- |
--
-- 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"