playlists 0.1.0.0 → 0.2.0.0
raw patch · 17 files changed
+435/−20 lines, 17 filesdep +doctestdep +filepathdep +hlintdep ~attoparsecdep ~basedep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: doctest, filepath, hlint
Dependency ranges changed: attoparsec, base, bytestring, hspec, optparse-applicative, text, word8
API changes (from Hackage documentation)
+ Text.Playlist: appendExtension :: Format -> FilePath -> FilePath
+ Text.Playlist: fileNameToFormat :: FilePath -> Maybe Format
Files
- LICENSE +1/−1
- README.md +42/−0
- playlists.cabal +70/−15
- src/Text/Playlist.hs +29/−0
- src/Text/Playlist/PLS/Writer.hs +4/−4
- test/Examples.hs +81/−0
- test/Helper.hs +35/−0
- test/M3USpec.hs +38/−0
- test/PLSSpec.hs +39/−0
- test/aa.m3u +2/−0
- test/ab.m3u +3/−0
- test/doctest.hs +36/−0
- test/hlint.hs +23/−0
- test/hp.m3u +6/−0
- test/pig.pls +11/−0
- test/sa.pls +12/−0
- test/utf8.pls +3/−0
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2013, Peter Jones <pjones@devalot.com>+Copyright (c) 2013,2014 Peter Jones <pjones@devalot.com> All rights reserved.
+ README.md view
@@ -0,0 +1,42 @@+# Haskell Playlists Library and Tool++Playlists is a library for working with media playlist files. The+original motivation for the library was extracting URLs for streaming+radio stations that use PLS and M3U playlist files.++The package also includes an executable that can dump the URLs from a+playlist file and convert between playlist file formats.++## Supported Formats++ * [PLS] []+ * [M3U and M3U8] [M3U]++## Future Plans++Some playlist files can be really big. I plan on adding support for+incremental parsing and generating via [io-streams] [] at some point+in the future.++## Library Example++ import qualified Data.ByteString as BS+ import Text.Playlist++ readPlaylist :: Format -> IO Playlist+ readPlaylist fmt = do+ content <- BS.getContents+ case parsePlaylist fmt content of+ Left err -> fail $ "failed to parse playlist on stdin: " ++ err+ Right x -> return x+++## Executable Example++ $ playlist urls --format PLS < somefile.pls++ $ playlist convert --from PLS --to M3U < somefile.pls++[pls]: http://en.wikipedia.org/wiki/PLS_(file_format)+[m3u]: http://en.wikipedia.org/wiki/M3U+[io-streams]: http://hackage.haskell.org/package/io-streams
playlists.cabal view
@@ -1,12 +1,12 @@ name: playlists-version: 0.1.0.0+version: 0.2.0.0 synopsis: Library and executable for working with playlist files. homepage: https://github.com/pjones/playlists license: BSD3 license-file: LICENSE author: Peter Jones <pjones@devalot.com> maintainer: Peter Jones <pjones@devalot.com>-copyright: Copyright (c) 2013 Peter Jones <pjones@devalot.com>+copyright: Copyright (c) 2013,2014 Peter Jones <pjones@devalot.com> category: Text build-type: Simple cabal-version: >=1.8@@ -37,10 +37,18 @@ > $ playlist convert --from PLS --to M3U < somefile.pls ----------------------------------------------------------------------------------- Work around a bug in Cabal < 1.16.0.3.-flag profiling- description: Build with executable profiling enabled- default: False+extra-source-files:+ README.md+ test/aa.m3u+ test/ab.m3u+ test/Examples.hs+ test/Helper.hs+ test/hp.m3u+ test/M3USpec.hs+ test/pig.pls+ test/PLSSpec.hs+ test/sa.pls+ test/utf8.pls -------------------------------------------------------------------------------- source-repository head@@ -48,6 +56,23 @@ location: https://github.com/pjones/playlists.git --------------------------------------------------------------------------------+flag maintainer+ description: Enable settings for the package maintainer.+ default: False++------------------------------------------------------------------------------+-- You can disable the hlint test suite with -f-test-hlint+flag test-hlint+ default: True+ manual: True++------------------------------------------------------------------------------+-- You can disable the doctest test suite with -f-test-doctest+flag test-doctest+ default: True+ manual: True++-------------------------------------------------------------------------------- library exposed-modules: Text.Playlist@@ -63,11 +88,16 @@ ghc-options: -Wall ghc-prof-options: -fprof-auto-top extensions: OverloadedStrings++ if flag(maintainer)+ ghc-options: -Werror+ build-depends: base >= 4.6 && < 5- , attoparsec >= 0.10 && < 0.11- , bytestring >= 0.10 && < 0.11- , text >= 0.11 && < 0.12- , word8 >= 0.0 && < 0.1+ , attoparsec >= 0.10 && < 1.0+ , bytestring >= 0.10 && < 1.0+ , text >= 0.11 && < 1.2+ , word8 >= 0.0 && < 1.0+ , filepath >= 1.3 && < 2.0 -------------------------------------------------------------------------------- executable playlist@@ -76,13 +106,12 @@ ghc-prof-options: -fprof-auto-top -fprof-cafs extensions: OverloadedStrings - -- Work around a bug in Cabal < 1.16.0.3- -- if flag(profiling)- -- ghc-options: -prof -fprof-auto-top -fprof-cafs+ if flag(maintainer)+ ghc-options: -Werror build-depends: base , bytestring- , optparse-applicative >= 0.5 && < 0.6+ , optparse-applicative >= 0.5 && < 1.0 , playlists , text @@ -95,5 +124,31 @@ main-is: Main.hs build-depends: base , bytestring- , hspec >= 1.4.0 && < 1.5+ , hspec >= 1.4.0 , playlists++------------------------------------------------------------------------------+test-suite hlint+ type: exitcode-stdio-1.0+ main-is: hlint.hs+ ghc-options: -w+ hs-source-dirs: test++ if !flag(test-hlint)+ buildable: False+ else+ build-depends: base+ , hlint >= 1.7++------------------------------------------------------------------------------+test-suite doctests+ type: exitcode-stdio-1.0+ ghc-options: -threaded+ main-is: doctest.hs+ hs-source-dirs: test++ if !flag(test-doctest)+ buildable: False+ else+ build-depends: base+ , doctest >= 0.9.11
src/Text/Playlist.hs view
@@ -19,6 +19,8 @@ -- * Parsing and Generating , parsePlaylist , generatePlaylist+ , fileNameToFormat+ , appendExtension ) where --------------------------------------------------------------------------------@@ -26,6 +28,7 @@ import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Builder as BL+import System.FilePath (takeExtension) import qualified Text.Playlist.M3U.Reader as M3U import qualified Text.Playlist.M3U.Writer as M3U import qualified Text.Playlist.PLS.Reader as PLS@@ -52,3 +55,29 @@ generatePlaylist :: Format -> Playlist -> BL.ByteString generatePlaylist M3U = BL.toLazyByteString . M3U.writePlaylist generatePlaylist PLS = BL.toLazyByteString . PLS.writePlaylist++--------------------------------------------------------------------------------+-- | Try to figure out a file's format from it's file extension.+--+-- >>> fileNameToFormat "foo.m3u"+-- Just M3U+--+-- >>> fileNameToFormat "foo.txt"+-- Nothing+fileNameToFormat :: FilePath -> Maybe Format+fileNameToFormat ext = case takeExtension ext of+ ".m3u" -> Just M3U+ ".m3u8" -> Just M3U+ ".pls" -> Just PLS+ _ -> Nothing++--------------------------------------------------------------------------------+-- | Given a file name that does not have a file extension, return a+-- file name with the appropriate extension included based on the+-- given format.+--+-- >>> appendExtension M3U "foo"+-- "foo.m3u"+appendExtension :: Format -> FilePath -> FilePath+appendExtension M3U = (++ ".m3u")+appendExtension PLS = (++ ".pls")
src/Text/Playlist/PLS/Writer.hs view
@@ -25,10 +25,10 @@ -------------------------------------------------------------------------------- writePlaylist :: Playlist -> Builder writePlaylist x =- B.byteString "[playlist]\n" <>- mconcat (map writeTrack $ zip x [1..]) <>- B.byteString "NumberOfEntries=" <>- B.stringUtf8 (show . length $ x) <>+ B.byteString "[playlist]\n" <>+ mconcat (zipWith (curry writeTrack) x [1..]) <>+ B.byteString "NumberOfEntries=" <>+ B.stringUtf8 (show . length $ x) <> B.byteString "\nVersion=2\n" --------------------------------------------------------------------------------
+ test/Examples.hs view
@@ -0,0 +1,81 @@+{-++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 Examples+ ( secretAgent+ , pigRadio+ , utf8Radio+ , hitParty+ , aaFile+ , abFile+ ) where++--------------------------------------------------------------------------------+import Text.Playlist++--------------------------------------------------------------------------------+secretAgent :: Playlist+secretAgent =+ [ Track { trackURL = "http://mp2.somafm.com:9016"+ , trackTitle = Just "SomaFM: Secret Agent (#1 128k mp3): The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"+ }++ , Track { trackURL = "http://mp3.somafm.com:443"+ , trackTitle = Just "SomaFM: Secret Agent (#2 128k mp3): The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"+ }++ , Track { trackURL = "http://ice.somafm.com/secretagent"+ , trackTitle = Just "SomaFM: Secret Agent (Firewall-friendly 128k mp3) The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"+ }+ ]++--------------------------------------------------------------------------------+pigRadio :: Playlist+pigRadio =+ [ Track { trackURL = "http://s6.mediastreaming.it:8080"+ , trackTitle = Just "Pig Radio - Devoted in Playing the Best Indie Pop/Rock & Electronic. 24/7"+ }++ , Track { trackURL = "http://s1.viastreaming.net:7480"+ , trackTitle = Just "Pig Radio - Devoted in Playing the Best Indie Pop/Rock & Electronic. 24/7"+ }+ ]++--------------------------------------------------------------------------------+utf8Radio :: Playlist+utf8Radio =+ [ Track { trackURL = "http://fake.com"+ , trackTitle = Just "Alle otto i bambini erano già in costume da bagno"+ }+ ]++--------------------------------------------------------------------------------+hitParty :: Playlist+hitParty =+ [ Track { trackURL = "http://firewall.hitparty.net:443"+ , trackTitle = Just "(#1 - Generique) HITPARTY HIT - Pas de pub Que du HIT - Only Hits"+ }+ , Track { trackURL = "http://icecast.pulsradio.com:80/hitpartyHD.mp3.m3u"+ , trackTitle = Just "(#1 - Generique) HITPARTY HIT - Pas de pub Que du HIT - Only Hits"+ }+ ]++--------------------------------------------------------------------------------+aaFile :: Playlist+aaFile =+ [ Track { trackURL = "http://foo.com", trackTitle = Nothing}+ , Track { trackURL = "http://bar.com", trackTitle = Nothing}+ ]++--------------------------------------------------------------------------------+abFile :: Playlist+abFile = aaFile
+ test/Helper.hs view
@@ -0,0 +1,35 @@+{-++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 Helper (playlistFromFile, roundTrip) where++--------------------------------------------------------------------------------+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import Data.Char (toLower)+import Text.Playlist++--------------------------------------------------------------------------------+playlistFromFile :: Format -> FilePath -> IO Playlist+playlistFromFile fmt file = do+ content <- BS.readFile file'+ case parsePlaylist fmt content of+ Left err -> fail $ "failed to parse: " ++ file' ++ ": " ++ err+ Right plst -> return plst+ where file' = "test/" ++ file ++ "." ++ map toLower (show fmt)++--------------------------------------------------------------------------------+roundTrip :: Format -> Playlist -> IO Playlist+roundTrip fmt plst =+ case parsePlaylist fmt $ BL.toStrict $ generatePlaylist fmt plst of+ Left err -> fail $ "failed to roundTrip playlist: " ++ err+ Right plst' -> return plst'
+ test/M3USpec.hs view
@@ -0,0 +1,38 @@+{-++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 M3USpec (spec) where++--------------------------------------------------------------------------------+import Examples (hitParty, aaFile, abFile)+import Helper (playlistFromFile, roundTrip)+import Test.Hspec+import Text.Playlist++--------------------------------------------------------------------------------+spec :: Spec+spec = do+ describe "Parsing" $ do+ it "HIT Party" $ playlistFromFile' "hp" `shouldReturn` hitParty+ it "File aa" $ playlistFromFile' "aa" `shouldReturn` aaFile+ it "File ab" $ playlistFromFile' "ab" `shouldReturn` abFile+ describe "Generating" $ do+ it "HIT Party" $ roundTrip' hitParty `shouldReturn` hitParty+ it "File aa" $ roundTrip' aaFile `shouldReturn` aaFile++--------------------------------------------------------------------------------+playlistFromFile' :: FilePath -> IO Playlist+playlistFromFile' = playlistFromFile M3U++--------------------------------------------------------------------------------+roundTrip' :: Playlist -> IO Playlist+roundTrip' = roundTrip M3U
+ test/PLSSpec.hs view
@@ -0,0 +1,39 @@+{-++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 PLSSpec (spec) where++--------------------------------------------------------------------------------+import Examples (secretAgent, pigRadio, utf8Radio)+import Helper (playlistFromFile, roundTrip)+import Test.Hspec+import Text.Playlist++--------------------------------------------------------------------------------+spec :: Spec+spec = do+ describe "Parsing" $ do+ it "Secret Agent" $ playlistFromFile' "sa" `shouldReturn` secretAgent+ it "Pig Radio" $ playlistFromFile' "pig" `shouldReturn` pigRadio+ it "UTF8 Radio" $ playlistFromFile' "utf8" `shouldReturn` utf8Radio+ describe "Generating" $ do+ it "Secret Agent" $ roundTrip' secretAgent `shouldReturn` secretAgent+ it "Pig Radio" $ roundTrip' pigRadio `shouldReturn` pigRadio+ it "UTF8 Radio" $ roundTrip' utf8Radio `shouldReturn` utf8Radio++--------------------------------------------------------------------------------+playlistFromFile' :: FilePath -> IO Playlist+playlistFromFile' = playlistFromFile PLS++--------------------------------------------------------------------------------+roundTrip' :: Playlist -> IO Playlist+roundTrip' = roundTrip PLS
+ test/aa.m3u view
@@ -0,0 +1,2 @@+http://foo.com+http://bar.com
+ test/ab.m3u view
@@ -0,0 +1,3 @@+http://foo.com+# This is a comment+http://bar.com
+ test/doctest.hs view
@@ -0,0 +1,36 @@+{-++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.++-}++--------------------------------------------------------------------------------+-- | Wrapper around doctest to make sure examples in the source code work.+module Main (main) where++--------------------------------------------------------------------------------+import Control.Applicative+import System.Environment+import Test.DocTest++--------------------------------------------------------------------------------+-- Totally stupid right now. Will search the directory later.+files :: IO [String]+files = fmap ("playlist.hs" :) files' where+ files' = return ["src/Text/Playlist.hs"]++--------------------------------------------------------------------------------+-- | GHC flags needed by doctest. Would be better to fetch these from+-- cabal directly though.+flags :: [String]+flags = ["-isrc", "-XOverloadedStrings"]++--------------------------------------------------------------------------------+-- | Check the docs.+main :: IO ()+main = doctest =<< (++) <$> getArgs <*> fmap (flags ++) files
+ test/hlint.hs view
@@ -0,0 +1,23 @@+-----------------------------------------------------------------------------+-- |+-- Module : Main (hlint)+-- Copyright : (C) 2013 Edward Kmett+-- License : BSD-style (see the file https://github.com/ekmett/succinct/blob/master/LICENSE)+-- Maintainer : Edward Kmett <ekmett@gmail.com>+-- Stability : provisional+-- Portability : portable+--+-- This module runs HLint on the lens source tree.+-----------------------------------------------------------------------------+module Main where++import Control.Monad+import Language.Haskell.HLint+import System.Environment+import System.Exit++main :: IO ()+main = do+ args <- getArgs+ hints <- hlint $ ["src", "playlist.hs", "--cpp-define=HLINT"] ++ args+ unless (null hints) exitFailure
+ test/hp.m3u view
@@ -0,0 +1,6 @@+#EXTM3U +#EXTINF:-1,(#1 - Generique) HITPARTY HIT - Pas de pub Que du HIT - Only Hits +http://firewall.hitparty.net:443 +#EXTINF:-1,(#1 - Generique) HITPARTY HIT - Pas de pub Que du HIT - Only Hits +http://icecast.pulsradio.com:80/hitpartyHD.mp3.m3u +
+ test/pig.pls view
@@ -0,0 +1,11 @@+[playlist] +File1=http://s6.mediastreaming.it:8080 +Title1=Pig Radio - Devoted in Playing the Best Indie Pop/Rock & Electronic. 24/7 +Length1=0+File2=http://s1.viastreaming.net:7480 +Title2=Pig Radio - Devoted in Playing the Best Indie Pop/Rock & Electronic. 24/7 +Length2=-1 + + +NumberOfEntries=2 +Version=2
+ test/sa.pls view
@@ -0,0 +1,12 @@+[playlist]+numberofentries=3+File1=http://mp2.somafm.com:9016+Title1=SomaFM: Secret Agent (#1 128k mp3): The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!+Length1=-1+File2=http://mp3.somafm.com:443+Title2=SomaFM: Secret Agent (#2 128k mp3): The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!+Length2=-1+File3=http://ice.somafm.com/secretagent+Title3=SomaFM: Secret Agent (Firewall-friendly 128k mp3) The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!+Length3=-1+Version=2
+ test/utf8.pls view
@@ -0,0 +1,3 @@+[playlist]+File1=http://fake.com+Title1=Alle otto i bambini erano già in costume da bagno