{-# LANGUAGE OverloadedStrings #-}

{-

This file is part of the Haskell package playlists. It is subject to
the license terms in the LICENSE file found in the top-level directory
of this distribution and at git://pmade.com/playlists/LICENSE. No part
of playlists package, including this file, may be copied, modified,
propagated, or distributed except according to the terms contained in
the LICENSE file.

-}

--------------------------------------------------------------------------------
module Text.Playlist.PLS.Reader (parsePlaylist) where

--------------------------------------------------------------------------------
import Control.Applicative
import Control.Monad (void)
import Data.Attoparsec.ByteString
import Data.Attoparsec.ByteString.Char8 (signed, double)
import Data.ByteString (ByteString)
import Data.Text (Text)
import Data.Text.Encoding (decodeUtf8)
import Data.Word8 (isDigit)
import Text.Playlist.Internal.Attoparsec
import Text.Playlist.Types

--------------------------------------------------------------------------------
-- | A parser that will process an entire playlist.
parsePlaylist :: Parser Playlist
parsePlaylist :: Parser Playlist
parsePlaylist = do
  Parser ()
parseHeader
  Playlist
ts <- Parser ByteString Track -> Parser Playlist
forall (f :: * -> *) a. Alternative f => f a -> f [a]
many1 Parser ByteString Track
parseTrack
  Parser () -> Parser ()
forall (f :: * -> *) a. Alternative f => f a -> f ()
skipMany Parser ()
skipUnusedLine
  Playlist -> Parser Playlist
forall a. a -> Parser ByteString a
forall (m :: * -> *) a. Monad m => a -> m a
return Playlist
ts

--------------------------------------------------------------------------------
-- | A pls header will at least contain the "[playlist]" bit but some
-- files also include the lines you'd expect in the footer too.
parseHeader :: Parser ()
parseHeader :: Parser ()
parseHeader = do
  Parser ()
skipSpace Parser ()
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ByteString -> Parser ByteString ByteString
string ByteString
"[playlist]" Parser ByteString ByteString -> Parser () -> Parser ()
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Parser ()
skipSpace
  Parser () -> Parser ()
forall (f :: * -> *) a. Alternative f => f a -> f ()
skipMany Parser ()
skipUnusedLine

--------------------------------------------------------------------------------
-- | Parse a single track.  Tracks begin with "FileN" where N is a
-- digit.  They are followed by an optional title and optional length.
parseTrack :: Parser Track
parseTrack :: Parser ByteString Track
parseTrack = do
  (ByteString
n, Text
url) <- Parser (ByteString, Text)
parseFileN
  Maybe Text
title    <- Parser ByteString Text -> Parser ByteString (Maybe Text)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional (ByteString -> Parser ByteString Text
parseTitle ByteString
n)

  -- Parse track length.
  Maybe Float
mlen <- (Float -> Maybe Float
forall a. a -> Maybe a
Just (Float -> Maybe Float)
-> (Double -> Float) -> Double -> Maybe Float
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Double -> Float
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> Maybe Float)
-> Parser ByteString Double -> Parser ByteString (Maybe Float)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Parser ()
skipSpace Parser ()
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ByteString -> Parser ByteString ByteString
string ByteString
"Length" Parser ByteString ByteString -> Parser () -> Parser ()
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> (Word8 -> Bool) -> Parser ()
skipWhile Word8 -> Bool
isDigit Parser ()
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ByteString -> Parser ByteString ByteString
string ByteString
"=" Parser ByteString ByteString
-> Parser ByteString Double -> Parser ByteString Double
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Parser ByteString Double -> Parser ByteString Double
forall a. Num a => Parser a -> Parser a
signed Parser ByteString Double
double))
      Parser ByteString (Maybe Float)
-> Parser ByteString (Maybe Float)
-> Parser ByteString (Maybe Float)
forall a.
Parser ByteString a -> Parser ByteString a -> Parser ByteString a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Maybe Float -> Parser ByteString (Maybe Float)
forall a. a -> Parser ByteString a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe Float
forall a. Maybe a
Nothing

  Track -> Parser ByteString Track
forall a. a -> Parser ByteString a
forall (m :: * -> *) a. Monad m => a -> m a
return Track { trackURL :: Text
trackURL      = Text
url
               , trackTitle :: Maybe Text
trackTitle    = Maybe Text
title
               , trackDuration :: Maybe Float
trackDuration = Maybe Float
mlen
               }

--------------------------------------------------------------------------------
-- | Skip any line that isn't part of a track.
skipUnusedLine :: Parser ()
skipUnusedLine :: Parser ()
skipUnusedLine =
  (ByteString -> Parser ByteString ByteString
string ByteString
"numberofentries" Parser ByteString ByteString
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall a.
Parser ByteString a -> Parser ByteString a -> Parser ByteString a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|>
   ByteString -> Parser ByteString ByteString
string ByteString
"NumberOfEntries" Parser ByteString ByteString
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall a.
Parser ByteString a -> Parser ByteString a -> Parser ByteString a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|>
   ByteString -> Parser ByteString ByteString
string ByteString
"version"         Parser ByteString ByteString
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall a.
Parser ByteString a -> Parser ByteString a -> Parser ByteString a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|>
   ByteString -> Parser ByteString ByteString
string ByteString
"Version") Parser ByteString ByteString -> Parser () -> Parser ()
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Parser ()
skipLine

--------------------------------------------------------------------------------
-- | Parser for the "FileN" line that contains the track number and
-- URL for the track.  The result is a pair where the first member is
-- the track number and the second member is the URL.
parseFileN :: Parser (ByteString, Text)
parseFileN :: Parser (ByteString, Text)
parseFileN = do
  Parser ()
skipSpace
  ByteString
n <- ByteString -> Parser ByteString ByteString
string ByteString
"File" Parser ByteString ByteString
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> (Word8 -> Bool) -> Parser ByteString ByteString
takeWhile1 Word8 -> Bool
isDigit
  Parser ()
skipEq
  ByteString
url <- (Word8 -> Bool) -> Parser ByteString ByteString
takeWhile1 (Bool -> Bool
not (Bool -> Bool) -> (Word8 -> Bool) -> Word8 -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Bool
isEOL)
  (ByteString, Text) -> Parser (ByteString, Text)
forall a. a -> Parser ByteString a
forall (m :: * -> *) a. Monad m => a -> m a
return (ByteString
n, ByteString -> Text
decodeUtf8 ByteString
url)

--------------------------------------------------------------------------------
-- | Parser for the title line with the given track number.
parseTitle :: ByteString -> Parser Text
parseTitle :: ByteString -> Parser ByteString Text
parseTitle ByteString
n = do
  Parser ()
skipSpace
  Parser ByteString ByteString -> Parser ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (ByteString -> Parser ByteString ByteString
string ByteString
"Title" Parser ByteString ByteString
-> Parser ByteString ByteString -> Parser ByteString ByteString
forall a b.
Parser ByteString a -> Parser ByteString b -> Parser ByteString b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ByteString -> Parser ByteString ByteString
string ByteString
n)
  Parser ()
skipEq
  ByteString -> Text
decodeUtf8 (ByteString -> Text)
-> Parser ByteString ByteString -> Parser ByteString Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Word8 -> Bool) -> Parser ByteString ByteString
takeWhile1 (Bool -> Bool
not (Bool -> Bool) -> (Word8 -> Bool) -> Word8 -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Bool
isEOL)