cue-sheet 2.0.1 → 2.0.2
raw patch · 11 files changed
+1154/−988 lines, 11 filesdep −semigroupsdep ~QuickCheckdep ~basedep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependencies removed: semigroups
Dependency ranges changed: QuickCheck, base, bytestring, containers, hspec, hspec-megaparsec, megaparsec, mtl, text
API changes (from Hackage documentation)
Files
- CHANGELOG.md +4/−0
- LICENSE.md +1/−1
- README.md +6/−13
- Text/CueSheet.hs +30/−28
- Text/CueSheet/Parser.hs +330/−264
- Text/CueSheet/Render.hs +42/−41
- Text/CueSheet/Types.hs +167/−174
- cue-sheet.cabal +80/−70
- tests/Text/CueSheet/ParserSpec.hs +385/−295
- tests/Text/CueSheet/RenderSpec.hs +99/−90
- tests/Text/CueSheet/TypesSpec.hs +10/−12
CHANGELOG.md view
@@ -1,3 +1,7 @@+## Cue sheet 2.0.2++* Builds with `mtl-2.3`.+ ## Cue sheet 2.0.1 * Got rid of `data-default-class` dependency, which was completely
LICENSE.md view
@@ -1,4 +1,4 @@-Copyright © 2016–2018 Mark Karpov+Copyright © 2016–present Mark Karpov All rights reserved.
README.md view
@@ -4,11 +4,12 @@ [](https://hackage.haskell.org/package/cue-sheet) [](http://stackage.org/nightly/package/cue-sheet) [](http://stackage.org/lts/package/cue-sheet)-[](https://travis-ci.org/mrkkrp/cue-sheet)+ -The library allows to construct, render, and parse CUE sheets.+The library provides support for construction, rendering, and parsing of CUE+sheets. -## What is CUE sheet?+## What is a CUE sheet? > A cue sheet, or cue file, is a metadata file which describes how the > tracks of a CD or DVD are laid out. Cue sheets are stored as plain text@@ -21,23 +22,15 @@ [here](https://wayback.archive.org/web/20070614044112/http://www.goldenhawk.com/download/cdrwin.pdf), scroll to the appendix A (it's closest we get to a “specification”). -## Quick start--[Read the Haddocks](https://hackage.haskell.org/package/cue-sheet). In-short, you parse a `CueSheet` with `parseCueSheet` function and render a-`CueSheet` with `renderCueSheet` function—pretty straightforward, eh? Of-course, you still can construct a `CueSheet` manually. The data types are-defined in such a way that incorrect CUE sheets are impossible to represent.- ## Contribution Issues, bugs, and questions may be reported in [the GitHub issue tracker for this project](https://github.com/mrkkrp/cue-sheet/issues). -Pull requests are also welcome and will be reviewed quickly.+Pull requests are also welcome. ## License -Copyright © 2016–2019 Mark Karpov+Copyright © 2016–present Mark Karpov Distributed under BSD 3 clause license.
Text/CueSheet.hs view
@@ -1,44 +1,46 @@ -- | -- Module : Text.CueSheet--- Copyright : © 2016–2019 Mark Karpov+-- Copyright : © 2016–present Mark Karpov -- License : BSD 3 clause -- -- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable ----- The module allows to construct, read, and write CUE sheets. The data--- types are defined in such a way that incorrect CUE sheets are impossible--- to represent. See 'parseCueSheet' for parsing of plain text CUE sheet+-- The module allows us to construct, read, and write CUE sheets. The data+-- types are defined in such a way that incorrect CUE sheets cannot be+-- represented. See 'parseCueSheet' for parsing of plain text CUE sheet -- files and 'renderCueSheet' for rendering.- module Text.CueSheet ( -- * Types- CueSheet (..)- , CueFile (..)- , CueFileType (..)- , CueTrack (..)- , CueTrackType (..)- , CueTime (..)- , fromMmSsFf- , toMmSsFf- , showMmSsFf- , Mcn- , mkMcn- , unMcn- , CueText- , mkCueText- , unCueText- , Isrc- , mkIsrc- , unIsrc- , CueSheetException (..)+ CueSheet (..),+ CueFile (..),+ CueFileType (..),+ CueTrack (..),+ CueTrackType (..),+ CueTime (..),+ fromMmSsFf,+ toMmSsFf,+ showMmSsFf,+ Mcn,+ mkMcn,+ unMcn,+ CueText,+ mkCueText,+ unCueText,+ Isrc,+ mkIsrc,+ unIsrc,+ CueSheetException (..),+ -- * Parsing- , parseCueSheet- , CueParserFailure (..)- , Eec (..)+ parseCueSheet,+ CueParserFailure (..),+ Eec (..),+ -- * Rendering- , renderCueSheet )+ renderCueSheet,+ ) where import Text.CueSheet.Parser
Text/CueSheet/Parser.hs view
@@ -1,6 +1,13 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+ -- | -- Module : Text.CueSheet.Parser--- Copyright : © 2016–2019 Mark Karpov+-- Copyright : © 2016–present Mark Karpov -- License : BSD 3 clause -- -- Maintainer : Mark Karpov <markkarpov92@gmail.com>@@ -9,27 +16,25 @@ -- -- The modules contains a CUE sheet parser. You probably want to import -- "Text.CueSheet" instead.--{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}- module Text.CueSheet.Parser- ( parseCueSheet- , CueParserFailure (..)- , Eec (..) )+ ( parseCueSheet,+ CueParserFailure (..),+ Eec (..),+ ) where -import Control.Monad.State.Strict+import Control.Monad (void)+import Control.Monad.State.Strict (StateT, execStateT, gets, modify) import Data.ByteString (ByteString) import Data.Data (Data) import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NE import Data.Maybe (isJust) import Data.Set (Set)+import qualified Data.Set as E import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T import Data.Typeable (Typeable) import Data.Word (Word8) import GHC.Generics@@ -37,10 +42,6 @@ import Text.CueSheet.Types import Text.Megaparsec import Text.Megaparsec.Byte-import qualified Data.List.NonEmpty as NE-import qualified Data.Set as E-import qualified Data.Text as T-import qualified Data.Text.Encoding as T import qualified Text.Megaparsec.Byte.Lexer as L ----------------------------------------------------------------------------@@ -48,42 +49,42 @@ -- | Extended error component with support for storing number of track -- declaration in which a parsing error has occurred.- data Eec = Eec (Maybe Natural) CueParserFailure deriving (Show, Eq, Ord, Data, Typeable, Generic) instance ShowErrorComponent Eec where showErrorComponent (Eec mtrack failure') =- showErrorComponent failure' ++ "\n" ++- maybe "" (\n -> "in declaration of the track " ++ show n) mtrack+ showErrorComponent failure'+ ++ "\n"+ ++ maybe "" (\n -> "in declaration of the track " ++ show n) mtrack -- | The enumeration of all failures that may happen during running of -- 'parseCueSheet'.- data CueParserFailure- = CueParserTrivialError (Maybe (ErrorItem Word8)) (Set (ErrorItem Word8))- -- ^ A wrapper for a trivial error- | CueParserInvalidCatalog Text- -- ^ We ran into an invalid media catalog number- | CueParserInvalidCueText Text- -- ^ We ran into an invalid text literal- | CueParserTrackOutOfOrder- -- ^ We spotted a track out of order- | CueParserInvalidTrackIsrc Text- -- ^ We ran into an invalid ISRC- | CueParserInvalidSeconds Natural- -- ^ We ran into an invalid number of seconds- | CueParserInvalidFrames Natural- -- ^ We ran into an invalid number of frames- | CueParserTrackIndexOutOfOrder- -- ^ We spotted a track index out of order+ = -- | A wrapper for a trivial error+ CueParserTrivialError (Maybe (ErrorItem Word8)) (Set (ErrorItem Word8))+ | -- | We ran into an invalid media catalog number+ CueParserInvalidCatalog Text+ | -- | We ran into an invalid text literal+ CueParserInvalidCueText Text+ | -- | We spotted a track out of order+ CueParserTrackOutOfOrder+ | -- | We ran into an invalid ISRC+ CueParserInvalidTrackIsrc Text+ | -- | We ran into an invalid number of seconds+ CueParserInvalidSeconds Natural+ | -- | We ran into an invalid number of frames+ CueParserInvalidFrames Natural+ | -- | We spotted a track index out of order+ CueParserTrackIndexOutOfOrder deriving (Show, Eq, Ord, Data, Typeable, Generic) instance ShowErrorComponent CueParserFailure where showErrorComponent = \case CueParserTrivialError us es ->- init $ parseErrorTextPretty- (TrivialError undefined us es :: ParseError ByteString Eec)+ init $+ parseErrorTextPretty+ (TrivialError undefined us es :: ParseError ByteString Eec) CueParserInvalidCatalog txt -> "the value \"" ++ T.unpack txt ++ "\" is not a valid Media Catalog Number" CueParserInvalidCueText txt ->@@ -100,129 +101,159 @@ "this index appears out of order" -- | Type of parser we use here, it's not public.- type Parser a = StateT Context (Parsec Eec ByteString) a -- | Context of parsing. This is passed around in 'StateT'. We need all of -- this to signal parse errors on duplicate declarations of things that -- should only be declared once according to description of the format, to -- validate track numbers, etc.- data Context = Context- { contextCueSheet :: !CueSheet- -- ^ Current state of CUE sheet we parse. When a part\/parameter of CUE+ { -- | Current state of CUE sheet we parse. When a part\/parameter of CUE -- sheet is parsed, this thing is updated.- , contextFiles :: ![CueFile]- -- ^ Temporary storage for parsed files (we can't store it in the+ contextCueSheet :: !CueSheet,+ -- | Temporary storage for parsed files (we can't store it in the -- 'CueSheet' because it does not allow empty list of files).- , contextTracks :: ![CueTrack]- -- ^ Similar to 'contextFiles', collection of tracks but for current+ contextFiles :: ![CueFile],+ -- | Similar to 'contextFiles', collection of tracks but for current -- file.- , contextTrackCount :: !Natural- -- ^ Number of tracks we have parsed so far, to avoid traversing lists+ contextTracks :: ![CueTrack],+ -- | Number of tracks we have parsed so far, to avoid traversing lists -- again and again.- , contextIndices :: ![CueTime]- -- ^ Temporary storage for collection indices for current track.- , contextIndexCount :: !Natural- -- ^ Similarly for indices.+ contextTrackCount :: !Natural,+ -- | Temporary storage for collection indices for current track.+ contextIndices :: ![CueTime],+ -- | Similarly for indices.+ contextIndexCount :: !Natural } -- | Parse a CUE sheet from a lazy 'BL.ByteString'.--parseCueSheet- :: String -- ^ File name to include in error messages- -> ByteString -- ^ CUE sheet to parse as a lazy 'BL.ByteString'- -> Either (ParseErrorBundle ByteString Eec) CueSheet -- ^ 'ParseError' or result+parseCueSheet ::+ -- | File name to include in error messages+ String ->+ -- | CUE sheet to parse as a lazy 'BL.ByteString'+ ByteString ->+ -- | 'ParseError' or result+ Either (ParseErrorBundle ByteString Eec) CueSheet parseCueSheet = parse (contextCueSheet <$> execStateT pCueSheet initContext) where- initContext = Context- { contextCueSheet = CueSheet- { cueCatalog = Nothing- , cueCdTextFile = Nothing- , cuePerformer = Nothing- , cueTitle = Nothing- , cueSongwriter = Nothing- , cueFirstTrackNumber = 0- , cueFiles = dummyFile :| [] }- , contextFiles = []- , contextTracks = []- , contextTrackCount = 0- , contextIndices = []- , contextIndexCount = 0 }+ initContext =+ Context+ { contextCueSheet =+ CueSheet+ { cueCatalog = Nothing,+ cueCdTextFile = Nothing,+ cuePerformer = Nothing,+ cueTitle = Nothing,+ cueSongwriter = Nothing,+ cueFirstTrackNumber = 0,+ cueFiles = dummyFile :| []+ },+ contextFiles = [],+ contextTracks = [],+ contextTrackCount = 0,+ contextIndices = [],+ contextIndexCount = 0+ } -- | Parse a 'CueSheet'. The result is not returned, but written in the -- 'Context'.- pCueSheet :: Parser () pCueSheet = do scn void (many pHeaderItem) void (some pFile) -- NOTE Lens would help here, but let's keep this vanilla.- modify $ \x -> x { contextCueSheet =- (contextCueSheet x)- { cueFiles = (NE.fromList . reverse . contextFiles) x } }+ modify $ \x ->+ x+ { contextCueSheet =+ (contextCueSheet x)+ { cueFiles = (NE.fromList . reverse . contextFiles) x+ }+ } eof pHeaderItem :: Parser ()-pHeaderItem = choice- [ pCatalog- , pCdTextFile- , pPerformer- , pTitle- , pSongwriter- , pRem ]+pHeaderItem =+ choice+ [ pCatalog,+ pCdTextFile,+ pPerformer,+ pTitle,+ pSongwriter,+ pRem+ ] pCatalog :: Parser () pCatalog = do already <- gets (isJust . cueCatalog . contextCueSheet)- let f x' = let x = T.decodeUtf8 x' in- case mkMcn x of- Nothing -> Left (CueParserInvalidCatalog x)- Just mcn -> Right mcn+ let f x' =+ let x = T.decodeUtf8 x'+ in case mkMcn x of+ Nothing -> Left (CueParserInvalidCatalog x)+ Just mcn -> Right mcn mcn <- labelledLit already f "CATALOG"- modify $ \x -> x { contextCueSheet =- (contextCueSheet x) { cueCatalog = Just mcn } }+ modify $ \x ->+ x+ { contextCueSheet =+ (contextCueSheet x) {cueCatalog = Just mcn}+ } pCdTextFile :: Parser () pCdTextFile = do already <- gets (isJust . cueCdTextFile . contextCueSheet) cdTextFile <- T.decodeUtf8 <$> labelledLit already Right "CDTEXTFILE"- modify $ \x -> x { contextCueSheet = (contextCueSheet x)- { cueCdTextFile = Just (T.unpack cdTextFile) } }+ modify $ \x ->+ x+ { contextCueSheet =+ (contextCueSheet x)+ { cueCdTextFile = Just (T.unpack cdTextFile)+ }+ } pPerformer :: Parser () pPerformer = do already <- gets (isJust . cuePerformer . contextCueSheet)- let f x' = let x = T.decodeUtf8 x' in- case mkCueText x of- Nothing -> Left (CueParserInvalidCueText x)- Just txt -> Right txt+ let f x' =+ let x = T.decodeUtf8 x'+ in case mkCueText x of+ Nothing -> Left (CueParserInvalidCueText x)+ Just txt -> Right txt performer <- labelledLit already f "PERFORMER"- modify $ \x -> x { contextCueSheet =- (contextCueSheet x) { cuePerformer = Just performer } }+ modify $ \x ->+ x+ { contextCueSheet =+ (contextCueSheet x) {cuePerformer = Just performer}+ } pTitle :: Parser () pTitle = do already <- gets (isJust . cueTitle . contextCueSheet)- let f x' = let x = T.decodeUtf8 x' in- case mkCueText x of- Nothing -> Left (CueParserInvalidCueText x)- Just txt -> Right txt+ let f x' =+ let x = T.decodeUtf8 x'+ in case mkCueText x of+ Nothing -> Left (CueParserInvalidCueText x)+ Just txt -> Right txt title <- labelledLit already f "TITLE"- modify $ \x -> x { contextCueSheet =- (contextCueSheet x) { cueTitle = Just title } }+ modify $ \x ->+ x+ { contextCueSheet =+ (contextCueSheet x) {cueTitle = Just title}+ } pSongwriter :: Parser () pSongwriter = do already <- gets (isJust . cueSongwriter . contextCueSheet)- let f x' = let x = T.decodeUtf8 x' in- case mkCueText x of- Nothing -> Left (CueParserInvalidCueText x)- Just txt -> Right txt+ let f x' =+ let x = T.decodeUtf8 x'+ in case mkCueText x of+ Nothing -> Left (CueParserInvalidCueText x)+ Just txt -> Right txt songwriter <- labelledLit already f "SONGWRITER"- modify $ \x -> x { contextCueSheet =- (contextCueSheet x) { cueSongwriter = Just songwriter } }+ modify $ \x ->+ x+ { contextCueSheet =+ (contextCueSheet x) {cueSongwriter = Just songwriter}+ } pRem :: Parser () pRem = do@@ -233,82 +264,103 @@ pFile = do void (symbol "FILE") filename <- T.decodeUtf8 <$> lexeme stringLit- let pFiletype = choice- [ Binary <$ symbol "BINARY"- , Motorola <$ symbol "MOTOROLA"- , Aiff <$ symbol "AIFF"- , Wave <$ symbol "WAVE"- , MP3 <$ symbol "MP3" ]+ let pFiletype =+ choice+ [ Binary <$ symbol "BINARY",+ Motorola <$ symbol "MOTOROLA",+ Aiff <$ symbol "AIFF",+ Wave <$ symbol "WAVE",+ MP3 <$ symbol "MP3"+ ] filetype <- pFiletype <* eol <* scn void (some (pTrack <|> pRem)) tracks <- gets contextTracks- let newFile = CueFile- { cueFileName = T.unpack filename- , cueFileType = filetype- , cueFileTracks = NE.fromList (reverse tracks) }- modify $ \x -> x- { contextFiles = newFile : contextFiles x- , contextTracks = [] }+ let newFile =+ CueFile+ { cueFileName = T.unpack filename,+ cueFileType = filetype,+ cueFileTracks = NE.fromList (reverse tracks)+ }+ modify $ \x ->+ x+ { contextFiles = newFile : contextFiles x,+ contextTracks = []+ } pTrack :: Parser () pTrack = do void (symbol "TRACK") trackOffset <- gets (cueFirstTrackNumber . contextCueSheet)- trackCount <- gets contextTrackCount+ trackCount <- gets contextTrackCount let firstTrack = trackCount == 0 f x = if firstTrack || x == trackOffset + trackCount then Right x else Left CueParserTrackOutOfOrder n <- withCheck f (lexeme L.decimal)- let pTrackType = choice- [ CueTrackAudio <$ symbol "AUDIO"- , CueTrackCdg <$ symbol "CDG"- , CueTrackMode1_2048 <$ symbol "MODE1/2048"- , CueTrackMode1_2352 <$ symbol "MODE1/2352"- , CueTrackMode2_2336 <$ symbol "MODE2/2336"- , CueTrackMode2_2352 <$ symbol "MODE2/2352"- , CueTrackCdi2336 <$ symbol "CDI/2336"- , CueTrackCdi2352 <$ symbol "CDI/2352" ]+ let pTrackType =+ choice+ [ CueTrackAudio <$ symbol "AUDIO",+ CueTrackCdg <$ symbol "CDG",+ CueTrackMode1_2048 <$ symbol "MODE1/2048",+ CueTrackMode1_2352 <$ symbol "MODE1/2352",+ CueTrackMode2_2336 <$ symbol "MODE2/2336",+ CueTrackMode2_2352 <$ symbol "MODE2/2352",+ CueTrackCdi2336 <$ symbol "CDI/2336",+ CueTrackCdi2352 <$ symbol "CDI/2352"+ ] trackType <- pTrackType <* eol <* scn- let newTrack = dummyTrack { cueTrackType = trackType }- modify $ \x -> x- { contextTracks = newTrack : contextTracks x- , contextTrackCount = trackCount + 1- , contextCueSheet = let old = contextCueSheet x in- if firstTrack- then old { cueFirstTrackNumber = n }- else old }+ let newTrack = dummyTrack {cueTrackType = trackType}+ modify $ \x ->+ x+ { contextTracks = newTrack : contextTracks x,+ contextTrackCount = trackCount + 1,+ contextCueSheet =+ let old = contextCueSheet x+ in if firstTrack+ then old {cueFirstTrackNumber = n}+ else old+ } inTrack n $ do void (many pTrackHeaderItem) index0 <- (optional . try . pIndex) 0- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- t { cueTrackPregapIndex = index0 } }- modify $ \x -> x- { contextIndices = []- , contextIndexCount = 0 }+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ t {cueTrackPregapIndex = index0}+ }+ modify $ \x ->+ x+ { contextIndices = [],+ contextIndexCount = 0+ } let grabIndex = do next <- (+ 1) <$> gets contextIndexCount nextIndex <- pIndex next- modify $ \x -> x- { contextIndices = nextIndex : contextIndices x- , contextIndexCount = next }+ modify $ \x ->+ x+ { contextIndices = nextIndex : contextIndices x,+ contextIndexCount = next+ } void (some (grabIndex <|> pRem))- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- t { cueTrackIndices = (NE.fromList . reverse . contextIndices) x } }+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ t {cueTrackIndices = (NE.fromList . reverse . contextIndices) x}+ } void (optional pPostgap) pTrackHeaderItem :: Parser ()-pTrackHeaderItem = choice- [ pFlags- , pIsrc- , pTrackPerformer- , pTrackTitle- , pTrackSongwriter- , pRem- , pPregap ]+pTrackHeaderItem =+ choice+ [ pFlags,+ pIsrc,+ pTrackPerformer,+ pTrackTitle,+ pTrackSongwriter,+ pRem,+ pPregap+ ] pFlags :: Parser () pFlags = do@@ -317,89 +369,108 @@ void (some pFlag) <* eol <* scn -- | A helper data type for track flags.- data CueTrackFlag = DCP | FourCH | PRE | SCMS pFlag :: Parser () pFlag = do- flag <- choice- [ DCP <$ symbol "DCP"- , FourCH <$ symbol "4CH"- , PRE <$ symbol "PRE"- , SCMS <$ symbol "SCMS" ]- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- case flag of- DCP -> t { cueTrackDigitalCopyPermitted = True }- FourCH -> t { cueTrackFourChannelAudio = True }- PRE -> t { cueTrackPreemphasisEnabled = True }- SCMS -> t { cueTrackSerialCopyManagement = True } }+ flag <-+ choice+ [ DCP <$ symbol "DCP",+ FourCH <$ symbol "4CH",+ PRE <$ symbol "PRE",+ SCMS <$ symbol "SCMS"+ ]+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ case flag of+ DCP -> t {cueTrackDigitalCopyPermitted = True}+ FourCH -> t {cueTrackFourChannelAudio = True}+ PRE -> t {cueTrackPreemphasisEnabled = True}+ SCMS -> t {cueTrackSerialCopyManagement = True}+ } pIsrc :: Parser () pIsrc = do already <- gets (isJust . cueTrackIsrc . head . contextTracks)- let f x' = let x = T.decodeUtf8 x' in- case mkIsrc x of- Nothing -> Left (CueParserInvalidTrackIsrc x)- Just isrc -> Right isrc+ let f x' =+ let x = T.decodeUtf8 x'+ in case mkIsrc x of+ Nothing -> Left (CueParserInvalidTrackIsrc x)+ Just isrc -> Right isrc isrc <- labelledLit already f "ISRC"- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- t { cueTrackIsrc = Just isrc } }+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ t {cueTrackIsrc = Just isrc}+ } pTrackPerformer :: Parser () pTrackPerformer = do already <- gets (isJust . cueTrackPerformer . head . contextTracks)- let f x' = let x = T.decodeUtf8 x' in- case mkCueText x of- Nothing -> Left (CueParserInvalidCueText x)- Just txt -> Right txt+ let f x' =+ let x = T.decodeUtf8 x'+ in case mkCueText x of+ Nothing -> Left (CueParserInvalidCueText x)+ Just txt -> Right txt performer <- labelledLit already f "PERFORMER"- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- t { cueTrackPerformer = Just performer } }+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ t {cueTrackPerformer = Just performer}+ } pTrackTitle :: Parser () pTrackTitle = do already <- gets (isJust . cueTrackTitle . head . contextTracks)- let f x' = let x = T.decodeUtf8 x' in- case mkCueText x of- Nothing -> Left (CueParserInvalidCueText x)- Just txt -> Right txt+ let f x' =+ let x = T.decodeUtf8 x'+ in case mkCueText x of+ Nothing -> Left (CueParserInvalidCueText x)+ Just txt -> Right txt title <- labelledLit already f "TITLE"- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- t { cueTrackTitle = Just title } }+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ t {cueTrackTitle = Just title}+ } pTrackSongwriter :: Parser () pTrackSongwriter = do already <- gets (isJust . cueTrackSongwriter . head . contextTracks)- let f x' = let x = T.decodeUtf8 x' in- case mkCueText x of- Nothing -> Left (CueParserInvalidCueText x)- Just txt -> Right txt+ let f x' =+ let x = T.decodeUtf8 x'+ in case mkCueText x of+ Nothing -> Left (CueParserInvalidCueText x)+ Just txt -> Right txt songwriter <- labelledLit already f "SONGWRITER"- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- t { cueTrackSongwriter = Just songwriter } }+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ t {cueTrackSongwriter = Just songwriter}+ } pPregap :: Parser () pPregap = do already <- gets (isJust . cueTrackPregap . head . contextTracks) failAtIf already "PREGAP" time <- lexeme cueTime <* eol <* scn- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- t { cueTrackPregap = Just time } }+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ t {cueTrackPregap = Just time}+ } pPostgap :: Parser () pPostgap = do already <- gets (isJust . cueTrackPostgap . head . contextTracks) failAtIf already "POSTGAP" time <- lexeme cueTime <* eol <* scn- modify $ \x -> x- { contextTracks = changingFirstOf (contextTracks x) $ \t ->- t { cueTrackPostgap = Just time } }+ modify $ \x ->+ x+ { contextTracks = changingFirstOf (contextTracks x) $ \t ->+ t {cueTrackPostgap = Just time}+ } pIndex :: Natural -> Parser CueTime pIndex n = do@@ -425,20 +496,17 @@ else Left (CueParserInvalidFrames n) seconds <- withCheck checkSeconds naturalLit void (char 58)- frames <- withCheck checkFrames naturalLit+ frames <- withCheck checkFrames naturalLit case fromMmSsFf minutes seconds frames of Nothing -> empty -- NOTE must be always valid, we checked already- Just x -> return x+ Just x -> return x ---------------------------------------------------------------------------- -- Helpers --- | Parse a thing and then check if it's OK conceptually. If it's not OK,--- the error will be reported with position at the start of offending--- lexeme, otherwise the lexeme is parsed as usual. Of course if the lexeme--- has incorrect format, that is just reported and no additional check--- happens.-+-- | Parse something and then check if it's OK. If it's not OK, the error+-- will be reported with position at the start of the offending lexeme,+-- otherwise the lexeme is parsed as usual. withCheck :: (a -> Either CueParserFailure b) -> Parser a -> Parser b withCheck check p = do o <- getOffset@@ -452,7 +520,6 @@ -- | If the first argument is 'True' and we can parse the given command, -- fail pointing at the beginning of the command and report it as something -- unexpected.- failAtIf :: Bool -> ByteString -> Parser () failAtIf shouldFail command = do let p = void (symbol command)@@ -464,106 +531,105 @@ -- | Indicate that the inner parser belongs to declaration of a track with -- the given index. The index of the track will be added to 'ParseError's to -- help the user find where the error happened.- inTrack :: Natural -> Parser a -> Parser a inTrack n = region f where- f (TrivialError pos us es) = FancyError pos . E.singleton $- ErrorCustom (Eec (Just n) (CueParserTrivialError us es))+ f (TrivialError pos us es) =+ FancyError pos . E.singleton $+ ErrorCustom (Eec (Just n) (CueParserTrivialError us es)) f (FancyError pos xs) = FancyError pos (E.map g xs) g (ErrorCustom (Eec mn x)) = ErrorCustom (Eec (mn <|> Just n) x) g e = e -- | A labelled literal (a helper for common case).--labelledLit- :: Bool -- ^ Should we instantly fail when command is parsed?- -> (ByteString -> Either CueParserFailure a) -- ^ How to judge the result- -> ByteString -- ^ Name of the command to grab- -> Parser a+labelledLit ::+ -- | Should we instantly fail when command is parsed?+ Bool ->+ -- | How to judge the result+ (ByteString -> Either CueParserFailure a) ->+ -- | Name of the command to grab+ ByteString ->+ Parser a labelledLit shouldFail check command = do failAtIf shouldFail command withCheck check (lexeme stringLit) <* eol <* scn -- | String literal with support for quotation.- stringLit :: Parser ByteString-stringLit = (quoted <?> "quoted string literal")- <|> (unquoted <?> "unquoted string literal")+stringLit =+ (quoted <?> "quoted string literal")+ <|> (unquoted <?> "unquoted string literal") where- quoted = char 34 *> takeWhileP Nothing f <* char 34+ quoted = char 34 *> takeWhileP Nothing f <* char 34 unquoted = takeWhileP Nothing g- f x = x /= 10 && x /= 34- g x = x /= 10 && x /= 9 && x /= 13 && x /= 32+ f x = x /= 10 && x /= 34+ g x = x /= 10 && x /= 9 && x /= 13 && x /= 32 -- | Parse a 'Natural'.- naturalLit :: Parser Natural naturalLit = L.decimal -- | Case-insensitive symbol parser.- symbol :: ByteString -> Parser ByteString symbol s = string' s <* notFollowedBy alphaNumChar <* sc -- | A wrapper for lexemes.- lexeme :: Parser a -> Parser a lexeme = L.lexeme sc -- | Space consumer (eats newlines).- scn :: Parser () scn = L.space space1 empty empty -- | Space consumer (does not eat newlines).- sc :: Parser () sc = L.space (void $ takeWhile1P Nothing f) empty empty where f x = x == 32 || x == 9 -- | Determine by 'CueTrack' if we have already parsed FLAGS command.- seenFlags :: CueTrack -> Bool-seenFlags CueTrack {..} = or- [ cueTrackDigitalCopyPermitted- , cueTrackFourChannelAudio- , cueTrackPreemphasisEnabled- , cueTrackSerialCopyManagement ]+seenFlags CueTrack {..} =+ or+ [ cueTrackDigitalCopyPermitted,+ cueTrackFourChannelAudio,+ cueTrackPreemphasisEnabled,+ cueTrackSerialCopyManagement+ ] -- | Apply given function to the first element of the list.- changingFirstOf :: [a] -> (a -> a) -> [a] changingFirstOf [] _ = []-changingFirstOf (x:xs) f = f x : xs+changingFirstOf (x : xs) f = f x : xs ---------------------------------------------------------------------------- -- Dummies -- | A dummy file. It's only here because 'CueSheet' can't have an empty -- list of files and it cannot be a bottom either.- dummyFile :: CueFile-dummyFile = CueFile- { cueFileName = ""- , cueFileType = Wave- , cueFileTracks = dummyTrack :| [] }+dummyFile =+ CueFile+ { cueFileName = "",+ cueFileType = Wave,+ cueFileTracks = dummyTrack :| []+ } -- | A dummy track, see 'dummyFile'.- dummyTrack :: CueTrack-dummyTrack = CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Nothing- , cueTrackPerformer = Nothing- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = CueTime 0 :| []- , cueTrackPostgap = Nothing }+dummyTrack =+ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Nothing,+ cueTrackPerformer = Nothing,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = CueTime 0 :| [],+ cueTrackPostgap = Nothing+ }
Text/CueSheet/Render.hs view
@@ -1,6 +1,10 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+ -- | -- Module : Text.CueSheet.Render--- Copyright : © 2016–2019 Mark Karpov+-- Copyright : © 2016–present Mark Karpov -- License : BSD 3 clause -- -- Maintainer : Mark Karpov <markkarpov92@gmail.com>@@ -9,47 +13,45 @@ -- -- The module contains a CUE sheet render. You probably want to import -- "Text.CueSheet" instead.--{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}- module Text.CueSheet.Render- ( renderCueSheet )+ ( renderCueSheet,+ ) where import Control.Monad import Control.Monad.State import Control.Monad.Writer.Lazy-import Data.Char (isSpace, chr)+import qualified Data.ByteString as B+import qualified Data.ByteString.Builder as BB+import qualified Data.ByteString.Lazy as BL+import Data.Char (chr, isSpace)+import qualified Data.List.NonEmpty as NE import Data.Maybe (catMaybes) import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T import Numeric.Natural import Text.CueSheet.Types import Text.Printf (printf)-import qualified Data.ByteString as B-import qualified Data.ByteString.Builder as BB-import qualified Data.ByteString.Lazy as BL-import qualified Data.List.NonEmpty as NE-import qualified Data.Text as T-import qualified Data.Text.Encoding as T -- | Render a CUE sheet as a lazy 'BL.ByteString'. All 'Text' values in the -- 'CueSheet' will be UTF-8 encoded.--renderCueSheet- :: Bool -- ^ Use CRLF sequence as “end of line” separator- -> CueSheet -- ^ The 'CueSheet' to render- -> BL.ByteString -- ^ The result+renderCueSheet ::+ -- | Use CRLF sequence as “end of line” separator+ Bool ->+ -- | The 'CueSheet' to render+ CueSheet ->+ -- | The result+ BL.ByteString renderCueSheet csrf CueSheet {..} = BB.toLazyByteString . execWriter . flip evalStateT (max cueFirstTrackNumber 1) $ do- let eol = tell (if csrf then "\r\n" else "\n")+ let eol = tell (if csrf then "\r\n" else "\n") tellText x = let raw = T.encodeUtf8 x- in tell . BB.byteString $- if B.any (isSpace . chr . fromIntegral) raw || B.null raw- then "\"" <> raw <> "\""- else raw+ in tell . BB.byteString $+ if B.any (isSpace . chr . fromIntegral) raw || B.null raw+ then "\"" <> raw <> "\""+ else raw forM_ cueCatalog $ \x -> do tell "CATALOG " tellText (unMcn x)@@ -84,11 +86,13 @@ tell " " tellText (renderTrackType cueTrackType) eol- let flags = catMaybes- [ if cueTrackDigitalCopyPermitted then Just "DCP" else Nothing- , if cueTrackFourChannelAudio then Just "4CH" else Nothing- , if cueTrackPreemphasisEnabled then Just "PRE" else Nothing- , if cueTrackSerialCopyManagement then Just "SCMS" else Nothing ]+ let flags =+ catMaybes+ [ if cueTrackDigitalCopyPermitted then Just "DCP" else Nothing,+ if cueTrackFourChannelAudio then Just "4CH" else Nothing,+ if cueTrackPreemphasisEnabled then Just "PRE" else Nothing,+ if cueTrackSerialCopyManagement then Just "SCMS" else Nothing+ ] unless (null flags) $ do tell " FLAGS " (tell . BB.byteString) (B.intercalate " " flags)@@ -117,7 +121,7 @@ tell " INDEX 00 " tellText (showMmSsFf x) eol- forM_ (NE.zip (NE.fromList [1..]) cueTrackIndices) $ \(n, index) -> do+ forM_ (NE.zip (NE.fromList [1 ..]) cueTrackIndices) $ \(n, index) -> do tell " INDEX " tellText (formatNat n) tell " "@@ -132,27 +136,24 @@ -- Helpers -- | Format a 'Natural' padding it with zeros to 2 digits.- formatNat :: Natural -> Text formatNat = T.pack . printf "%02d" -- | Render a 'CueFileType' as per the CUE specs.- renderFileType :: CueFileType -> Text-renderFileType Binary = "BINARY"+renderFileType Binary = "BINARY" renderFileType Motorola = "MOTOROLA"-renderFileType Aiff = "AIFF"-renderFileType Wave = "WAVE"-renderFileType MP3 = "MP3"+renderFileType Aiff = "AIFF"+renderFileType Wave = "WAVE"+renderFileType MP3 = "MP3" -- | Render a 'CueTrackType' as per the CUE specs.- renderTrackType :: CueTrackType -> Text-renderTrackType CueTrackAudio = "AUDIO"-renderTrackType CueTrackCdg = "CDG"+renderTrackType CueTrackAudio = "AUDIO"+renderTrackType CueTrackCdg = "CDG" renderTrackType CueTrackMode1_2048 = "MODE1/2048" renderTrackType CueTrackMode1_2352 = "MODE1/2352" renderTrackType CueTrackMode2_2336 = "MODE2/2336" renderTrackType CueTrackMode2_2352 = "MODE2/2352"-renderTrackType CueTrackCdi2336 = "CDI/2336"-renderTrackType CueTrackCdi2352 = "CDI/2352"+renderTrackType CueTrackCdi2336 = "CDI/2336"+renderTrackType CueTrackCdi2352 = "CDI/2352"
Text/CueSheet/Types.hs view
@@ -1,6 +1,8 @@+{-# LANGUAGE DeriveGeneric #-}+ -- | -- Module : Text.CueSheet.Types--- Copyright : © 2016–2019 Mark Karpov+-- Copyright : © 2016–present Mark Karpov -- License : BSD 3 clause -- -- Maintainer : Mark Karpov <markkarpov92@gmail.com>@@ -9,181 +11,185 @@ -- -- Types describing structure of a CUE sheet. You probably want to import -- "Text.CueSheet" instead.--{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveGeneric #-}- module Text.CueSheet.Types- ( CueSheet (..)- , CueFile (..)- , CueFileType (..)- , CueTrack (..)- , CueTrackType (..)- , CueTime (..)- , fromMmSsFf- , toMmSsFf- , showMmSsFf- , Mcn- , mkMcn- , unMcn- , CueText- , mkCueText- , unCueText- , Isrc- , mkIsrc- , unIsrc- , CueSheetException (..) )+ ( CueSheet (..),+ CueFile (..),+ CueFileType (..),+ CueTrack (..),+ CueTrackType (..),+ CueTime (..),+ fromMmSsFf,+ toMmSsFf,+ showMmSsFf,+ Mcn,+ mkMcn,+ unMcn,+ CueText,+ mkCueText,+ unCueText,+ Isrc,+ mkIsrc,+ unIsrc,+ CueSheetException (..),+ ) where import Control.Monad.Catch-import Data.Char (isDigit, isAscii, isLetter)+import Data.Char (isAscii, isDigit, isLetter) import Data.List.NonEmpty (NonEmpty (..))-import Data.Monoid ((<>))+import qualified Data.List.NonEmpty as NE import Data.Text (Text)+import qualified Data.Text as T import GHC.Generics import Numeric.Natural import Test.QuickCheck import Text.Printf (printf)-import qualified Data.List.NonEmpty as NE-import qualified Data.Text as T --- | Entire CUE sheet, contains one or more files (see 'CueFile').-+-- | CUE sheet, contains one or more files (see 'CueFile'). data CueSheet = CueSheet- { cueCatalog :: !(Maybe Mcn)- -- ^ Disc's Media Catalog Number (see 'Mcn').- , cueCdTextFile :: !(Maybe FilePath)- -- ^ Name of the file that contains the encoded CD-Text information for+ { -- | Disc's Media Catalog Number (see 'Mcn').+ cueCatalog :: !(Maybe Mcn),+ -- | Name of the file that contains the encoded CD-Text information for -- the disc.- , cuePerformer :: !(Maybe CueText)- -- ^ Performer of the entire disc.- , cueTitle :: !(Maybe CueText)- -- ^ Title of the entire disc.- , cueSongwriter :: !(Maybe CueText)- -- ^ Songwriter of the entire disc.- , cueFirstTrackNumber :: !Natural- -- ^ Number of the first track. Typically 1, but may be greater than 1.- , cueFiles :: !(NonEmpty CueFile)- -- ^ Collection of files to be written.- } deriving (Show, Eq, Ord, Generic)+ cueCdTextFile :: !(Maybe FilePath),+ -- | Performer of the entire disc.+ cuePerformer :: !(Maybe CueText),+ -- | Title of the entire disc.+ cueTitle :: !(Maybe CueText),+ -- | Songwriter of the entire disc.+ cueSongwriter :: !(Maybe CueText),+ -- | Number of the first track. Typically 1, but may be greater than 1.+ cueFirstTrackNumber :: !Natural,+ -- | Collection of files to be written.+ cueFiles :: !(NonEmpty CueFile)+ }+ deriving (Show, Eq, Ord, Generic) instance Arbitrary CueSheet where- arbitrary = CueSheet- <$> arbitrary- <*> oneof [pure Nothing, Just <$> filepath]- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> (fromInteger . getPositive <$> arbitrary)- <*> scaleDown (NE.fromList . getNonEmpty <$> arbitrary)+ arbitrary =+ CueSheet+ <$> arbitrary+ <*> oneof [pure Nothing, Just <$> filepath]+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> (fromInteger . getPositive <$> arbitrary)+ <*> scaleDown (NE.fromList . getNonEmpty <$> arbitrary) -- | A file to be written. Single file can be divided into one or more -- tracks (see 'CueTrack').- data CueFile = CueFile- { cueFileName :: !FilePath- -- ^ Name of file.- , cueFileType :: !CueFileType- -- ^ Type of file.- , cueFileTracks :: !(NonEmpty CueTrack)- -- ^ Collection of tracks in the file.- } deriving (Show, Eq, Ord, Generic)+ { -- | Name of file.+ cueFileName :: !FilePath,+ -- | Type of file.+ cueFileType :: !CueFileType,+ -- | Collection of tracks in the file.+ cueFileTracks :: !(NonEmpty CueTrack)+ }+ deriving (Show, Eq, Ord, Generic) instance Arbitrary CueFile where- arbitrary = CueFile- <$> filepath- <*> arbitrary- <*> scaleDown (NE.fromList . getNonEmpty <$> arbitrary)+ arbitrary =+ CueFile+ <$> filepath+ <*> arbitrary+ <*> scaleDown (NE.fromList . getNonEmpty <$> arbitrary) -- | Enumeration of audio or file's data types.- data CueFileType- = Binary- -- ^ Intel binary file (least significant byte first). Use for data+ = -- | Intel binary file (least significant byte first). Use for data -- files.- | Motorola- -- ^ Motorola binary file (most significant file first). Use for data+ Binary+ | -- | Motorola binary file (most significant file first). Use for data -- files.- | Aiff- -- ^ Audio AIFF file (44.1 kHz, 16 bit stereo).- | Wave- -- ^ Audio WAVE file (44.1 kHz, 16 bit stereo).- | MP3- -- ^ Audio MP3 file (44.1 kHz 16 bit stereo).+ Motorola+ | -- | Audio AIFF file (44.1 kHz, 16 bit stereo).+ Aiff+ | -- | Audio WAVE file (44.1 kHz, 16 bit stereo).+ Wave+ | -- | Audio MP3 file (44.1 kHz 16 bit stereo).+ MP3 deriving (Show, Read, Eq, Ord, Bounded, Enum, Generic) instance Arbitrary CueFileType where- arbitrary = elements [minBound..maxBound]+ arbitrary = elements [minBound .. maxBound] -- | A track. Single track can have one or more indices.- data CueTrack = CueTrack- { cueTrackDigitalCopyPermitted :: !Bool- -- ^ Flag: digital copy permitted.- , cueTrackFourChannelAudio :: !Bool- -- ^ Flag: four channel audio.- , cueTrackPreemphasisEnabled :: !Bool- -- ^ Flag: pre-emphasis enabled (audio track only).- , cueTrackSerialCopyManagement :: !Bool- -- ^ Flag: serial copy management system (not supported by all+ { -- | Flag: digital copy permitted.+ cueTrackDigitalCopyPermitted :: !Bool,+ -- | Flag: four channel audio.+ cueTrackFourChannelAudio :: !Bool,+ -- | Flag: pre-emphasis enabled (audio track only).+ cueTrackPreemphasisEnabled :: !Bool,+ -- | Flag: serial copy management system (not supported by all -- recorders).- , cueTrackType :: !CueTrackType- -- ^ Type datatype.- , cueTrackIsrc :: !(Maybe Isrc)- -- ^ The track's International Standard Recording Code (ISRC).- , cueTrackTitle :: !(Maybe CueText)- -- ^ Title of the track.- , cueTrackPerformer :: !(Maybe CueText)- -- ^ Performer of the track.- , cueTrackSongwriter :: !(Maybe CueText)- -- ^ Songwriter of the track.- , cueTrackPregap :: !(Maybe CueTime)- -- ^ Track's pregap.- , cueTrackPregapIndex :: !(Maybe CueTime)- -- ^ Starting time of track pregap, a.k.a. INDEX 0.- , cueTrackIndices :: !(NonEmpty CueTime)- -- ^ Collection of indices for the track starting with index 1. The+ cueTrackSerialCopyManagement :: !Bool,+ -- | Type datatype.+ cueTrackType :: !CueTrackType,+ -- | The track's International Standard Recording Code (ISRC).+ cueTrackIsrc :: !(Maybe Isrc),+ -- | Title of the track.+ cueTrackTitle :: !(Maybe CueText),+ -- | Performer of the track.+ cueTrackPerformer :: !(Maybe CueText),+ -- | Songwriter of the track.+ cueTrackSongwriter :: !(Maybe CueText),+ -- | Track's pregap.+ cueTrackPregap :: !(Maybe CueTime),+ -- | Starting time of track pregap, a.k.a. INDEX 0.+ cueTrackPregapIndex :: !(Maybe CueTime),+ -- | Collection of indices for the track starting with index 1. The -- index specifies the starting time of the track data. Index 1 is the -- only index that's stored in the disc's table of contents.- , cueTrackPostgap :: !(Maybe CueTime)- -- ^ Track's postgap.- } deriving (Show, Eq, Ord, Generic)+ cueTrackIndices :: !(NonEmpty CueTime),+ -- | Track's postgap.+ cueTrackPostgap :: !(Maybe CueTime)+ }+ deriving (Show, Eq, Ord, Generic) instance Arbitrary CueTrack where- arbitrary = CueTrack- <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> scaleDown (NE.fromList . getNonEmpty <$> arbitrary)- <*> arbitrary+ arbitrary =+ CueTrack+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> scaleDown (NE.fromList . getNonEmpty <$> arbitrary)+ <*> arbitrary -- | Track datatype.- data CueTrackType- = CueTrackAudio -- ^ Audio\/Music (2352).- | CueTrackCdg -- ^ Karaoke CD+G (2448).- | CueTrackMode1_2048 -- ^ CD-ROM Mode1 data (cooked).- | CueTrackMode1_2352 -- ^ CD-ROM Mode1 data (raw).- | CueTrackMode2_2336 -- ^ CD-ROM XA Mode2 data.- | CueTrackMode2_2352 -- ^ CD-ROM XA Mode2 data.- | CueTrackCdi2336 -- ^ CD-I Mode2 data.- | CueTrackCdi2352 -- ^ CD-I Mode2 data.+ = -- | Audio\/Music (2352).+ CueTrackAudio+ | -- | Karaoke CD+G (2448).+ CueTrackCdg+ | -- | CD-ROM Mode1 data (cooked).+ CueTrackMode1_2048+ | -- | CD-ROM Mode1 data (raw).+ CueTrackMode1_2352+ | -- | CD-ROM XA Mode2 data.+ CueTrackMode2_2336+ | -- | CD-ROM XA Mode2 data.+ CueTrackMode2_2352+ | -- | CD-I Mode2 data.+ CueTrackCdi2336+ | -- | CD-I Mode2 data.+ CueTrackCdi2352 deriving (Show, Read, Eq, Ord, Bounded, Enum, Generic) instance Arbitrary CueTrackType where- arbitrary = elements [minBound..maxBound]+ arbitrary = elements [minBound .. maxBound] -- | This datatype is used to indicate duration and position in time. It -- contains number of frames. There are 75 frames in one second.- newtype CueTime = CueTime Natural deriving (Show, Read, Eq, Ord, Generic) @@ -193,38 +199,39 @@ -- | Construct 'CueTime' from minutes, seconds, and frames. There are 75 -- frames per second. If number of seconds or frames is invalid, -- 'InvalidSeconds' or 'InvalidFrames' will be thrown.--fromMmSsFf :: MonadThrow m- => Natural -- ^ Number of minutes, no limit here- -> Natural -- ^ Number of seconds, 0–59 inclusive- -> Natural -- ^ Number of frames, 0–74 inclusive- -> m CueTime -- ^ The result+fromMmSsFf ::+ MonadThrow m =>+ -- | Number of minutes, no limit here+ Natural ->+ -- | Number of seconds, 0–59 inclusive+ Natural ->+ -- | Number of frames, 0–74 inclusive+ Natural ->+ -- | The result+ m CueTime fromMmSsFf mm ss ff- | ss >= 60 = throwM (InvalidSeconds ss)- | ff >= 75 = throwM (InvalidFrames ff)+ | ss >= 60 = throwM (InvalidSeconds ss)+ | ff >= 75 = throwM (InvalidFrames ff) | otherwise =- let ss' = mm * 60 + ss- ff' = ss' * 75 + ff- in return (CueTime ff')+ let ss' = mm * 60 + ss+ ff' = ss' * 75 + ff+ in return (CueTime ff') -- | Get minutes, seconds, and frames from a 'CueTime' value.- toMmSsFf :: CueTime -> (Natural, Natural, Natural)-toMmSsFf (CueTime ff') = (mm,ss,ff)+toMmSsFf (CueTime ff') = (mm, ss, ff) where (ss', ff) = ff' `quotRem` 75- (mm, ss) = ss' `quotRem` 60+ (mm, ss) = ss' `quotRem` 60 -- | Render representation of 'CueTime' in @mm:ss:ff@ format.- showMmSsFf :: CueTime -> Text showMmSsFf x = T.pack (printf "%02d:%02d:%02d" mm ss ff) where- (mm,ss,ff) = toMmSsFf x+ (mm, ss, ff) = toMmSsFf x -- | Disc's Media Catalog Number (MCN), must be 13 characters long, all the -- characters must be numeric.- newtype Mcn = Mcn Text deriving (Eq, Ord, Generic) @@ -236,7 +243,6 @@ -- | Make a 'Mcn'. If the provided 'Text' value is not a valid MCN, throw -- the 'InvalidMcnException'.- mkMcn :: MonadThrow m => Text -> m Mcn mkMcn x = if isValidMcn x@@ -244,7 +250,6 @@ else throwM (InvalidMcn x) -- | Get 'Text' from 'Mcn'.- unMcn :: Mcn -> Text unMcn (Mcn x) = x @@ -252,7 +257,6 @@ -- between 1 and 80 characters as per spec. We also demand that it does not -- contain @\"@ and newline characters, as it's not clear from the spec how -- to escape them properly.- newtype CueText = CueText Text deriving (Eq, Ord, Generic) @@ -264,7 +268,6 @@ -- | Make a 'CueText'. If the provided 'Text' value is not a valid CUE text, -- throw the 'InvalidCueText' exception.- mkCueText :: MonadThrow m => Text -> m CueText mkCueText x = if isValidCueText x@@ -272,14 +275,12 @@ else throwM (InvalidCueText x) -- | Get 'Text' from 'CueText'.- unCueText :: CueText -> Text unCueText (CueText x) = x -- | The track's International Standard Recording Code (ISRC). It must be 12 -- characters in length. The first five characters are alphanumeric, the -- last seven are numeric only.- newtype Isrc = Isrc Text deriving (Eq, Ord, Generic) @@ -294,34 +295,31 @@ -- | Make an 'Isrc', if the provided 'Text' value is not a valid ISRC, throw -- the 'InvalidIsrc' exception.- mkIsrc :: MonadThrow m => Text -> m Isrc mkIsrc x =- if T.length x == 12 &&- T.all isAlphaNum (T.take 5 x) &&- T.all isDigit (T.drop 5 x)+ if T.length x == 12+ && T.all isAlphaNum (T.take 5 x)+ && T.all isDigit (T.drop 5 x) then return (Isrc x) else throwM (InvalidIsrc x) -- | Get 'Text' from 'Isrc'.- unIsrc :: Isrc -> Text unIsrc (Isrc x) = x -- | Exception type for the bad things that may happen while you use the -- library.- data CueSheetException- = InvalidSeconds Natural- -- ^ The value is greater than 59 and thus is invalid for 'fromMmSsFf'.- | InvalidFrames Natural- -- ^ The value is greater than 74 and thus is invalid for 'fromMmSsFf'.- | InvalidMcn Text- -- ^ Provided text wasn't a correct media catalog number (MCN).- | InvalidCueText Text- -- ^ Provided text wasn't a valid CUE text.- | InvalidIsrc Text- -- ^ Provided text wasn't a valid ISRC.+ = -- | The value is greater than 59 and thus is invalid for 'fromMmSsFf'.+ InvalidSeconds Natural+ | -- | The value is greater than 74 and thus is invalid for 'fromMmSsFf'.+ InvalidFrames Natural+ | -- | Provided text wasn't a correct media catalog number (MCN).+ InvalidMcn Text+ | -- | Provided text wasn't a valid CUE text.+ InvalidCueText Text+ | -- | Provided text wasn't a valid ISRC.+ InvalidIsrc Text deriving (Eq, Ord, Show, Read, Generic) instance Exception CueSheetException@@ -330,13 +328,11 @@ -- Helpers -- | Check if the given 'Text' is a valid MCN.- isValidMcn :: Text -> Bool isValidMcn x = T.length x == 13 && T.all isDigit x -- | Check if the given 'Text' has valid length and contents to be used in a -- CUE sheet as performer, title, etc.- isValidCueText :: Text -> Bool isValidCueText x = l >= 1 && l <= 80 && T.all f x where@@ -345,17 +341,14 @@ -- | A variant of 'Data.Char.IsAlphaNum' that only permits ASCII letter -- chars.- isAlphaNum :: Char -> Bool isAlphaNum a = isAscii a && (isDigit a || isLetter a) -- | Scale down size of 'arbitrary'-generated stuff.- scaleDown :: Gen a -> Gen a scaleDown = scale (`quot` 3) -- | File path generator.- filepath :: Gen FilePath filepath = listOf (arbitrary `suchThat` windowsLikesIt) where
cue-sheet.cabal view
@@ -1,78 +1,88 @@-name: cue-sheet-version: 2.0.1-cabal-version: 1.18-tested-with: GHC==7.10.3, GHC==8.0.2, GHC==8.2.2, GHC==8.4.4, GHC==8.6.3-license: BSD3-license-file: LICENSE.md-author: Mark Karpov <markkarpov92@gmail.com>-maintainer: Mark Karpov <markkarpov92@gmail.com>-homepage: https://github.com/mrkkrp/cue-sheet-bug-reports: https://github.com/mrkkrp/cue-sheet/issues-category: Audio-synopsis: Support for construction, rendering, and parsing of CUE sheets-build-type: Simple-description: Support for construction, rendering, and parsing of CUE sheets.-extra-doc-files: CHANGELOG.md- , README.md-data-files: cue-sheet-samples/*.cue+cabal-version: 2.4+name: cue-sheet+version: 2.0.2+license: BSD-3-Clause+license-file: LICENSE.md+maintainer: Mark Karpov <markkarpov92@gmail.com>+author: Mark Karpov <markkarpov92@gmail.com>+tested-with: ghc ==8.10.7 ghc ==9.0.2 ghc ==9.2.1+homepage: https://github.com/mrkkrp/cue-sheet+bug-reports: https://github.com/mrkkrp/cue-sheet/issues+synopsis:+ Support for construction, rendering, and parsing of CUE sheets +description:+ Support for construction, rendering, and parsing of CUE sheets.++category: Audio+build-type: Simple+data-files: cue-sheet-samples/*.cue+extra-doc-files:+ CHANGELOG.md+ README.md+ source-repository head- type: git- location: https://github.com/mrkkrp/cue-sheet.git+ type: git+ location: https://github.com/mrkkrp/cue-sheet.git flag dev- description: Turn on development settings.- manual: True- default: False+ description: Turn on development settings.+ default: False+ manual: True library- build-depends: QuickCheck >= 2.4 && < 3.0- , base >= 4.8 && < 5.0- , bytestring >= 0.10.8 && < 0.11- , containers >= 0.5 && < 0.7- , exceptions >= 0.6 && < 0.11- , megaparsec >= 7.0 && < 8.0- , mtl >= 2.0 && < 3.0- , text >= 0.2 && < 1.3- if !impl(ghc >= 8.0)- build-depends: semigroups == 0.18.*- exposed-modules: Text.CueSheet- , Text.CueSheet.Types- , Text.CueSheet.Parser- , Text.CueSheet.Render- if flag(dev)- ghc-options: -Wall -Werror- else- ghc-options: -O2 -Wall- if flag(dev) && impl(ghc >= 8.0)- ghc-options: -Wcompat- -Wincomplete-record-updates- -Wincomplete-uni-patterns- -Wnoncanonical-monad-instances- -Wnoncanonical-monadfail-instances- default-language: Haskell2010+ exposed-modules:+ Text.CueSheet+ Text.CueSheet.Types+ Text.CueSheet.Parser+ Text.CueSheet.Render + default-language: Haskell2010+ build-depends:+ QuickCheck >=2.4 && <3.0,+ base >=4.13 && <5.0,+ bytestring >=0.10.8 && <0.12,+ containers >=0.5 && <0.7,+ exceptions >=0.6 && <0.11,+ megaparsec >=7.0 && <10.0,+ mtl >=2.0 && <3.0,+ text >=0.2 && <1.3++ if flag(dev)+ ghc-options: -Wall -Werror++ else+ ghc-options: -O2 -Wall++ if flag(dev)+ ghc-options:+ -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns+ -Wnoncanonical-monad-instances+ test-suite tests- main-is: Spec.hs- other-modules: Text.CueSheet.ParserSpec- , Text.CueSheet.RenderSpec- , Text.CueSheet.TypesSpec- hs-source-dirs: tests- type: exitcode-stdio-1.0- build-depends: QuickCheck >= 2.4 && < 3.0- , base >= 4.8 && < 5.0- , bytestring >= 0.10.8 && < 0.11- , cue-sheet- , exceptions >= 0.6 && < 0.11- , hspec >= 2.0 && < 3.0- , hspec-megaparsec >= 2.0 && < 3.0- , megaparsec >= 7.0 && < 8.0- , text >= 0.2 && < 1.3- build-tools: hspec-discover >= 2.0 && < 3.0- if !impl(ghc >= 8.0)- build-depends: semigroups == 0.18.*- if flag(dev)- ghc-options: -Wall -Werror- else- ghc-options: -O2 -Wall- default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ build-tool-depends: hspec-discover:hspec-discover >=2.0 && <3.0+ hs-source-dirs: tests+ other-modules:+ Text.CueSheet.ParserSpec+ Text.CueSheet.RenderSpec+ Text.CueSheet.TypesSpec++ default-language: Haskell2010+ build-depends:+ QuickCheck >=2.4 && <3.0,+ base >=4.13 && <5.0,+ bytestring >=0.10.8 && <0.12,+ cue-sheet,+ exceptions >=0.6 && <0.11,+ hspec >=2.0 && <3.0,+ hspec-megaparsec >=2.0 && <3.0,+ megaparsec >=7.0 && <10.0,+ text >=0.2 && <1.3++ if flag(dev)+ ghc-options: -Wall -Werror++ else+ ghc-options: -O2 -Wall
tests/Text/CueSheet/ParserSpec.hs view
@@ -1,19 +1,16 @@ {-# LANGUAGE OverloadedStrings #-} -module Text.CueSheet.ParserSpec- ( spec )-where+module Text.CueSheet.ParserSpec (spec) where import Control.Monad.Catch import Data.ByteString (ByteString)-import Data.Monoid ((<>))+import qualified Data.ByteString as B+import qualified Data.List.NonEmpty as NE import Test.Hspec import Test.Hspec.Megaparsec import Text.CueSheet.Parser import Text.CueSheet.Types import Text.Megaparsec-import qualified Data.ByteString as B-import qualified Data.List.NonEmpty as NE spec :: Spec spec =@@ -32,226 +29,305 @@ parseCueSheet "" bs `shouldParse` sheet it "at least one file should be declared" $ do bs <- B.readFile "cue-sheet-samples/parser-missing-file.cue"- let es = foldMap etoks- [ "CATALOG"- , "CDTEXTFILE"- , "FILE"- , "PERFORMER"- , "REM"- , "SONGWRITER"- , "TITLE" ]- parseCueSheet "" bs `shouldFailWith`- err 67 (ueof <> es)+ let es =+ foldMap+ etoks+ [ "CATALOG",+ "CDTEXTFILE",+ "FILE",+ "PERFORMER",+ "REM",+ "SONGWRITER",+ "TITLE"+ ]+ parseCueSheet "" bs+ `shouldFailWith` err 67 (ueof <> es) it "at least one track should be declared" $ do bs <- B.readFile "cue-sheet-samples/parser-missing-track.cue"- parseCueSheet "" bs `shouldFailWith`- err 109 (ueof <> etoks "REM" <> etoks "TRACK")+ parseCueSheet "" bs+ `shouldFailWith` err 109 (ueof <> etoks "REM" <> etoks "TRACK") it "at least one non-zero index should be declared" $ do bs <- B.readFile "cue-sheet-samples/parser-missing-index.cue" let e = wrappedTrivial (ueof <> etoks "INDEX" <> etoks "REM")- parseCueSheet "" bs `shouldFailWith`- errFancy 174 (cstm (Eec (Just 1) e))+ parseCueSheet "" bs+ `shouldFailWith` errFancy 174 (cstm (Eec (Just 1) e)) it "parses a normal CUE file without issues" $ do bs <- B.readFile "cue-sheet-samples/parser-normal.cue" sheet <- normalCueSheet parseCueSheet "" bs `shouldParse` sheet it "rejects invalid catalog number" $ do bs <- B.readFile "cue-sheet-samples/parser-invalid-catalog.cue"- parseCueSheet "" bs `shouldFailWith` errFancy 8- (cstm (Eec Nothing (CueParserInvalidCatalog "123")))+ parseCueSheet "" bs+ `shouldFailWith` errFancy+ 8+ (cstm (Eec Nothing (CueParserInvalidCatalog "123"))) it "rejects invalid CUE text" $ do bs <- B.readFile "cue-sheet-samples/parser-invalid-text.cue"- parseCueSheet "" bs `shouldFailWith` errFancy 10- (cstm (Eec Nothing (CueParserInvalidCueText "")))+ parseCueSheet "" bs+ `shouldFailWith` errFancy+ 10+ (cstm (Eec Nothing (CueParserInvalidCueText ""))) it "detects and reports tracks that appear out of order" $ do bs <- B.readFile "cue-sheet-samples/parser-track-out-of-order.cue"- parseCueSheet "" bs `shouldFailWith` errFancy 182- (cstm (Eec Nothing CueParserTrackOutOfOrder))+ parseCueSheet "" bs+ `shouldFailWith` errFancy+ 182+ (cstm (Eec Nothing CueParserTrackOutOfOrder)) it "rejects invalid ISRC values" $ do bs <- B.readFile "cue-sheet-samples/parser-invalid-isrc.cue"- parseCueSheet "" bs `shouldFailWith` errFancy 161- (cstm (Eec (Just 3) (CueParserInvalidTrackIsrc "123")))+ parseCueSheet "" bs+ `shouldFailWith` errFancy+ 161+ (cstm (Eec (Just 3) (CueParserInvalidTrackIsrc "123"))) it "rejects invalid number of seconds" $ do bs <- B.readFile "cue-sheet-samples/parser-invalid-seconds.cue"- parseCueSheet "" bs `shouldFailWith` errFancy 168- (cstm (Eec (Just 1) (CueParserInvalidSeconds 61)))+ parseCueSheet "" bs+ `shouldFailWith` errFancy+ 168+ (cstm (Eec (Just 1) (CueParserInvalidSeconds 61))) it "rejects invalid number of frames" $ do bs <- B.readFile "cue-sheet-samples/parser-invalid-frames.cue"- parseCueSheet "" bs `shouldFailWith` errFancy 171- (cstm (Eec (Just 1) (CueParserInvalidFrames 77)))+ parseCueSheet "" bs+ `shouldFailWith` errFancy+ 171+ (cstm (Eec (Just 1) (CueParserInvalidFrames 77))) it "detects and reports indices that appear out of order" $ do bs <- B.readFile "cue-sheet-samples/parser-index-out-of-order.cue"- parseCueSheet "" bs `shouldFailWith` errFancy 162- (cstm (Eec (Just 1) CueParserTrackIndexOutOfOrder))+ parseCueSheet "" bs+ `shouldFailWith` errFancy+ 162+ (cstm (Eec (Just 1) CueParserTrackIndexOutOfOrder)) it "rejects duplicate CATALOG command" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-catalog.cue"- let es = utoks "CATA" <> foldMap etoks- [ "CDTEXTFILE"- , "FILE"- , "PERFORMER"- , "REM"- , "SONGWRITER"- , "TITLE" ]+ let es =+ utoks "CATA"+ <> foldMap+ etoks+ [ "CDTEXTFILE",+ "FILE",+ "PERFORMER",+ "REM",+ "SONGWRITER",+ "TITLE"+ ] parseCueSheet "" bs `shouldFailWith` err 22 es it "rejects duplicate CDTEXTFILE command" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-cdtextfile.cue"- let es = utoks "CDTE" <> foldMap etoks- [ "CATALOG"- , "FILE"- , "PERFORMER"- , "REM"- , "SONGWRITER"- , "TITLE" ]+ let es =+ utoks "CDTE"+ <> foldMap+ etoks+ [ "CATALOG",+ "FILE",+ "PERFORMER",+ "REM",+ "SONGWRITER",+ "TITLE"+ ] parseCueSheet "" bs `shouldFailWith` err 23 es it "rejects duplicate PERFORMER command" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-performer.cue"- let es = utoks "PERF" <> foldMap etoks- [ "CATALOG"- , "CDTEXTFILE"- , "FILE"- , "REM"- , "SONGWRITER"- , "TITLE" ]+ let es =+ utoks "PERF"+ <> foldMap+ etoks+ [ "CATALOG",+ "CDTEXTFILE",+ "FILE",+ "REM",+ "SONGWRITER",+ "TITLE"+ ] parseCueSheet "" bs `shouldFailWith` err 30 es it "rejects duplicate TITLE command" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-title.cue"- let es = utoks "TITL" <> foldMap etoks- [ "CATALOG"- , "CDTEXTFILE"- , "FILE"- , "PERFORMER"- , "REM"- , "SONGWRITER" ]+ let es =+ utoks "TITL"+ <> foldMap+ etoks+ [ "CATALOG",+ "CDTEXTFILE",+ "FILE",+ "PERFORMER",+ "REM",+ "SONGWRITER"+ ] parseCueSheet "" bs `shouldFailWith` err 17 es it "rejects duplicate SONGWRITER command" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-songwriter.cue"- let es = utoks "SONG" <> foldMap etoks- [ "CATALOG"- , "CDTEXTFILE"- , "FILE"- , "PERFORMER"- , "REM"- , "TITLE" ]+ let es =+ utoks "SONG"+ <> foldMap+ etoks+ [ "CATALOG",+ "CDTEXTFILE",+ "FILE",+ "PERFORMER",+ "REM",+ "TITLE"+ ] parseCueSheet "" bs `shouldFailWith` err 18 es it "rejects duplicate FLAGS command (in track)" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-flags.cue"- let es = wrappedTrivial $ utoks "FLAGS" <> foldMap etoks- [ "INDEX"- , "ISRC"- , "PERFORMER"- , "PREGAP"- , "REM"- , "SONGWRITER"- , "TITLE" ]- parseCueSheet "" bs `shouldFailWith`- errFancy 144 (cstm (Eec (Just 1) es))+ let es =+ wrappedTrivial $+ utoks "FLAGS"+ <> foldMap+ etoks+ [ "INDEX",+ "ISRC",+ "PERFORMER",+ "PREGAP",+ "REM",+ "SONGWRITER",+ "TITLE"+ ]+ parseCueSheet "" bs+ `shouldFailWith` errFancy 144 (cstm (Eec (Just 1) es)) it "rejects duplicate ISRC command (in track)" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-isrc.cue"- let es = wrappedTrivial $ utoks "ISRC " <> foldMap etoks- [ "FLAGS"- , "INDEX"- , "PERFORMER"- , "PREGAP"- , "REM"- , "SONGWRITER"- , "TITLE" ]- parseCueSheet "" bs `shouldFailWith`- errFancy 152 (cstm (Eec (Just 1) es))+ let es =+ wrappedTrivial $+ utoks "ISRC "+ <> foldMap+ etoks+ [ "FLAGS",+ "INDEX",+ "PERFORMER",+ "PREGAP",+ "REM",+ "SONGWRITER",+ "TITLE"+ ]+ parseCueSheet "" bs+ `shouldFailWith` errFancy 152 (cstm (Eec (Just 1) es)) it "rejects duplicate PERFORMER command (in track)" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-performer.cue"- let es = wrappedTrivial $ utoks "PERFO" <> foldMap etoks- [ "FLAGS"- , "INDEX"- , "ISRC"- , "PREGAP"- , "REM"- , "SONGWRITER"- , "TITLE" ]- parseCueSheet "" bs `shouldFailWith`- errFancy 156 (cstm (Eec (Just 1) es))+ let es =+ wrappedTrivial $+ utoks "PERFO"+ <> foldMap+ etoks+ [ "FLAGS",+ "INDEX",+ "ISRC",+ "PREGAP",+ "REM",+ "SONGWRITER",+ "TITLE"+ ]+ parseCueSheet "" bs+ `shouldFailWith` errFancy 156 (cstm (Eec (Just 1) es)) it "rejects duplicate TITLE command (in track)" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-title.cue"- let es = wrappedTrivial $ utoks "TITLE" <> foldMap etoks- [ "FLAGS"- , "INDEX"- , "ISRC"- , "PERFORMER"- , "PREGAP"- , "REM"- , "SONGWRITER" ]- parseCueSheet "" bs `shouldFailWith`- errFancy 146 (cstm (Eec (Just 1) es))+ let es =+ wrappedTrivial $+ utoks "TITLE"+ <> foldMap+ etoks+ [ "FLAGS",+ "INDEX",+ "ISRC",+ "PERFORMER",+ "PREGAP",+ "REM",+ "SONGWRITER"+ ]+ parseCueSheet "" bs+ `shouldFailWith` errFancy 146 (cstm (Eec (Just 1) es)) it "rejects duplicate SONGWRITER command (in track)" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-songwriter.cue"- let es = wrappedTrivial $ utoks "SONGW" <> foldMap etoks- [ "FLAGS"- , "INDEX"- , "ISRC"- , "PERFORMER"- , "PREGAP"- , "REM"- , "TITLE" ]- parseCueSheet "" bs `shouldFailWith`- errFancy 150 (cstm (Eec (Just 1) es))+ let es =+ wrappedTrivial $+ utoks "SONGW"+ <> foldMap+ etoks+ [ "FLAGS",+ "INDEX",+ "ISRC",+ "PERFORMER",+ "PREGAP",+ "REM",+ "TITLE"+ ]+ parseCueSheet "" bs+ `shouldFailWith` errFancy 150 (cstm (Eec (Just 1) es)) it "rejects duplicate PREGAP command (in track)" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-pregap.cue"- let es = wrappedTrivial $ utoks "PREGA" <> foldMap etoks- [ "FLAGS"- , "INDEX"- , "ISRC"- , "PERFORMER"- , "REM"- , "SONGWRITER"- , "TITLE" ]- parseCueSheet "" bs `shouldFailWith`- errFancy 176 (cstm (Eec (Just 1) es))+ let es =+ wrappedTrivial $+ utoks "PREGA"+ <> foldMap+ etoks+ [ "FLAGS",+ "INDEX",+ "ISRC",+ "PERFORMER",+ "REM",+ "SONGWRITER",+ "TITLE"+ ]+ parseCueSheet "" bs+ `shouldFailWith` errFancy 176 (cstm (Eec (Just 1) es)) it "rejects duplicate POSTGAP command (in track)" $ do bs <- B.readFile "cue-sheet-samples/parser-duplicate-track-postgap.cue"- let es = foldMap etoks- [ "FILE"- , "REM"- , "TRACK" ]- parseCueSheet "" bs `shouldFailWith`- err 199 (utok 80 <> es <> eeof)+ let es =+ foldMap+ etoks+ [ "FILE",+ "REM",+ "TRACK"+ ]+ parseCueSheet "" bs+ `shouldFailWith` err 199 (utok 80 <> es <> eeof) testSheet :: MonadThrow m => m CueSheet testSheet = do- performer <- mkCueText "Faithless"- title <- mkCueText "Live in Berlin"+ performer <- mkCueText "Faithless"+ title <- mkCueText "Live in Berlin" trackTitle <- mkCueText "Reverence"- return CueSheet- { cueCatalog = Nothing- , cueCdTextFile = Nothing- , cuePerformer = Just performer- , cueTitle = Just title- , cueSongwriter = Nothing- , cueFirstTrackNumber = 1- , cueFiles = NE.fromList- [ CueFile- { cueFileName = "Faithless - Live in Berlin.mp3"- , cueFileType = MP3- , cueFileTracks = NE.fromList- [ CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just trackTitle- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList- [CueTime 3, CueTime 151]- , cueTrackPostgap = Nothing } ] } ] }+ return+ CueSheet+ { cueCatalog = Nothing,+ cueCdTextFile = Nothing,+ cuePerformer = Just performer,+ cueTitle = Just title,+ cueSongwriter = Nothing,+ cueFirstTrackNumber = 1,+ cueFiles =+ NE.fromList+ [ CueFile+ { cueFileName = "Faithless - Live in Berlin.mp3",+ cueFileType = MP3,+ cueFileTracks =+ NE.fromList+ [ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just trackTitle,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices =+ NE.fromList+ [CueTime 3, CueTime 151],+ cueTrackPostgap = Nothing+ }+ ]+ }+ ]+ } normalCueSheet :: MonadThrow m => m CueSheet normalCueSheet = do- performer <- mkCueText "Faithless"- catalog <- mkMcn "1112223334445"- title <- mkCueText "Live in Berlin"+ performer <- mkCueText "Faithless"+ catalog <- mkMcn "1112223334445"+ title <- mkCueText "Live in Berlin" track1Title <- mkCueText "Reverence" track2Title <- mkCueText "She's My Baby" track3Title <- mkCueText "Take the Long Way Home"@@ -260,142 +336,156 @@ track6Title <- mkCueText "Salva Mea" track7Title <- mkCueText "Dirty Old Man" track8Title <- mkCueText "God Is a DJ"- return CueSheet- { cueCatalog = Just catalog- , cueCdTextFile = Just "blah-blah.txt"- , cuePerformer = Just performer- , cueTitle = Just title- , cueSongwriter = Nothing- , cueFirstTrackNumber = 1- , cueFiles = NE.fromList- [ CueFile- { cueFileName = "Faithless - Live in Berlin.mp3"- , cueFileType = MP3- , cueFileTracks = NE.fromList- [ CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just track1Title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Just (CueTime 75)- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList [CueTime 0]- , cueTrackPostgap = Nothing }- , CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just track2Title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList [CueTime 30150]- , cueTrackPostgap = Nothing }- , CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just track3Title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList [CueTime 49050]- , cueTrackPostgap = Just (CueTime 76) }- , CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just track4Title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList- [CueTime 76800, CueTime 76805]- , cueTrackPostgap = Nothing }- , CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = True- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just track5Title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList [CueTime 115800]- , cueTrackPostgap = Nothing }- , CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just track6Title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList [CueTime 138750]- , cueTrackPostgap = Nothing }- , CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just track7Title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList [CueTime 172800]- , cueTrackPostgap = Nothing }- , CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Nothing- , cueTrackTitle = Just track8Title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = NE.fromList [CueTime 191625]- , cueTrackPostgap = Nothing } ] } ] }+ return+ CueSheet+ { cueCatalog = Just catalog,+ cueCdTextFile = Just "blah-blah.txt",+ cuePerformer = Just performer,+ cueTitle = Just title,+ cueSongwriter = Nothing,+ cueFirstTrackNumber = 1,+ cueFiles =+ NE.fromList+ [ CueFile+ { cueFileName = "Faithless - Live in Berlin.mp3",+ cueFileType = MP3,+ cueFileTracks =+ NE.fromList+ [ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just track1Title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Just (CueTime 75),+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = NE.fromList [CueTime 0],+ cueTrackPostgap = Nothing+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just track2Title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = NE.fromList [CueTime 30150],+ cueTrackPostgap = Nothing+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just track3Title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = NE.fromList [CueTime 49050],+ cueTrackPostgap = Just (CueTime 76)+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just track4Title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices =+ NE.fromList+ [CueTime 76800, CueTime 76805],+ cueTrackPostgap = Nothing+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = True,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just track5Title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = NE.fromList [CueTime 115800],+ cueTrackPostgap = Nothing+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just track6Title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = NE.fromList [CueTime 138750],+ cueTrackPostgap = Nothing+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just track7Title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = NE.fromList [CueTime 172800],+ cueTrackPostgap = Nothing+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Nothing,+ cueTrackTitle = Just track8Title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = NE.fromList [CueTime 191625],+ cueTrackPostgap = Nothing+ }+ ]+ }+ ]+ } -- | Construct 'CueParserFailure' from given @'ET' 'Word8'@ thing.- wrappedTrivial :: ET ByteString -> CueParserFailure wrappedTrivial xs = case err 0 xs of TrivialError _ us es -> CueParserTrivialError us es- _ -> error "Ooops!"+ _ -> error "Ooops!" -- | Quickly make a custom error component.- cstm :: e -> EF e cstm = fancy . ErrorCustom {-# INLINE cstm #-}
tests/Text/CueSheet/RenderSpec.hs view
@@ -1,18 +1,16 @@ {-# LANGUAGE OverloadedStrings #-} -module Text.CueSheet.RenderSpec- ( spec )-where+module Text.CueSheet.RenderSpec (spec) where import Control.Monad.Catch+import qualified Data.ByteString.Lazy as BL import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NE import Test.Hspec import Test.QuickCheck import Text.CueSheet.Parser import Text.CueSheet.Render import Text.CueSheet.Types-import qualified Data.ByteString.Lazy as BL-import qualified Data.List.NonEmpty as NE spec :: Spec spec =@@ -21,11 +19,11 @@ property $ \csrf cueSheet -> do let r = renderCueSheet csrf cueSheet f x = if csrf then BL.take (BL.length x - 1) x else x- BL.split 10 r `shouldNotSatisfy`- any ((" " `BL.isSuffixOf`) . f)+ BL.split 10 r+ `shouldNotSatisfy` any ((" " `BL.isSuffixOf`) . f) it "always ends with the specified new line sequence" $ property $ \csrf cueSheet -> do- let r = renderCueSheet csrf cueSheet+ let r = renderCueSheet csrf cueSheet eol = if csrf then "\r\n" else "\n" r `shouldSatisfy` (eol `BL.isSuffixOf`) it "doesn't look too bad" $ do@@ -38,88 +36,99 @@ `shouldBe` Right cueSheet -- | A manually constructed testing CUE sheet.- testCueSheet :: MonadThrow m => m CueSheet testCueSheet = do- mcn <- mkMcn "1112223334445"- performer <- mkCueText "The Famous Foobar"- title <- mkCueText "Bobla Fett"+ mcn <- mkMcn "1112223334445"+ performer <- mkCueText "The Famous Foobar"+ title <- mkCueText "Bobla Fett" songwriter <- mkCueText "Paul"- isrc <- mkIsrc "agree1234567"- pregap <- fromMmSsFf 1 0 13- index <- fromMmSsFf 0 0 15- postgap <- fromMmSsFf 0 8 9- return CueSheet- { cueCatalog = Just mcn- , cueCdTextFile = Just "/home/mark/mycdtextfile.txt"- , cuePerformer = Just performer- , cueTitle = Just title- , cueSongwriter = Just songwriter- , cueFirstTrackNumber = 1- , cueFiles = NE.fromList- [ CueFile- { cueFileName = "the-famous-foobar-cd1.flac"- , cueFileType = Wave- , cueFileTracks = NE.fromList- [ CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = True- , cueTrackSerialCopyManagement = True- , cueTrackType = CueTrackAudio- , cueTrackIsrc = Just isrc- , cueTrackTitle = Just title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = index :| []- , cueTrackPostgap = Nothing }- , CueTrack- { cueTrackDigitalCopyPermitted = True- , cueTrackFourChannelAudio = True- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackCdg- , cueTrackIsrc = Just isrc- , cueTrackTitle = Just title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Just pregap- , cueTrackPregapIndex = Nothing- , cueTrackIndices = index :| []- , cueTrackPostgap = Just postgap } ] }- , CueFile- { cueFileName = "the-famous-foobar-cd2.mp3"- , cueFileType = MP3- , cueFileTracks = NE.fromList- [ CueTrack- { cueTrackDigitalCopyPermitted = False- , cueTrackFourChannelAudio = False- , cueTrackPreemphasisEnabled = False- , cueTrackSerialCopyManagement = False- , cueTrackType = CueTrackMode1_2048- , cueTrackIsrc = Just isrc- , cueTrackTitle = Just title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = index :| []- , cueTrackPostgap = Nothing }- , CueTrack- { cueTrackDigitalCopyPermitted = True- , cueTrackFourChannelAudio = True- , cueTrackPreemphasisEnabled = True- , cueTrackSerialCopyManagement = True- , cueTrackType = CueTrackMode2_2336- , cueTrackIsrc = Just isrc- , cueTrackTitle = Just title- , cueTrackPerformer = Just performer- , cueTrackSongwriter = Nothing- , cueTrackPregap = Nothing- , cueTrackPregapIndex = Nothing- , cueTrackIndices = index :| []- , cueTrackPostgap = Nothing } ] }- ]- }+ isrc <- mkIsrc "agree1234567"+ pregap <- fromMmSsFf 1 0 13+ index <- fromMmSsFf 0 0 15+ postgap <- fromMmSsFf 0 8 9+ return+ CueSheet+ { cueCatalog = Just mcn,+ cueCdTextFile = Just "/home/mark/mycdtextfile.txt",+ cuePerformer = Just performer,+ cueTitle = Just title,+ cueSongwriter = Just songwriter,+ cueFirstTrackNumber = 1,+ cueFiles =+ NE.fromList+ [ CueFile+ { cueFileName = "the-famous-foobar-cd1.flac",+ cueFileType = Wave,+ cueFileTracks =+ NE.fromList+ [ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = True,+ cueTrackSerialCopyManagement = True,+ cueTrackType = CueTrackAudio,+ cueTrackIsrc = Just isrc,+ cueTrackTitle = Just title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = index :| [],+ cueTrackPostgap = Nothing+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = True,+ cueTrackFourChannelAudio = True,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackCdg,+ cueTrackIsrc = Just isrc,+ cueTrackTitle = Just title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Just pregap,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = index :| [],+ cueTrackPostgap = Just postgap+ }+ ]+ },+ CueFile+ { cueFileName = "the-famous-foobar-cd2.mp3",+ cueFileType = MP3,+ cueFileTracks =+ NE.fromList+ [ CueTrack+ { cueTrackDigitalCopyPermitted = False,+ cueTrackFourChannelAudio = False,+ cueTrackPreemphasisEnabled = False,+ cueTrackSerialCopyManagement = False,+ cueTrackType = CueTrackMode1_2048,+ cueTrackIsrc = Just isrc,+ cueTrackTitle = Just title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = index :| [],+ cueTrackPostgap = Nothing+ },+ CueTrack+ { cueTrackDigitalCopyPermitted = True,+ cueTrackFourChannelAudio = True,+ cueTrackPreemphasisEnabled = True,+ cueTrackSerialCopyManagement = True,+ cueTrackType = CueTrackMode2_2336,+ cueTrackIsrc = Just isrc,+ cueTrackTitle = Just title,+ cueTrackPerformer = Just performer,+ cueTrackSongwriter = Nothing,+ cueTrackPregap = Nothing,+ cueTrackPregapIndex = Nothing,+ cueTrackIndices = index :| [],+ cueTrackPostgap = Nothing+ }+ ]+ }+ ]+ }
tests/Text/CueSheet/TypesSpec.hs view
@@ -1,13 +1,11 @@ {-# LANGUAGE OverloadedStrings #-} -module Text.CueSheet.TypesSpec- ( spec )-where+module Text.CueSheet.TypesSpec (spec) where +import qualified Data.Text as T import Test.Hspec import Test.QuickCheck import Text.CueSheet.Types-import qualified Data.Text as T spec :: Spec spec =@@ -30,14 +28,14 @@ it "produces the correct result" $ do fromMmSsFf 0 13 66 `shouldReturn` CueTime 1041 fromMmSsFf 3 44 10 `shouldReturn` CueTime 16810- fromMmSsFf 9 0 0 `shouldReturn` CueTime 40500+ fromMmSsFf 9 0 0 `shouldReturn` CueTime 40500 describe "showMmSsFf" $ it "works correctly" $ do let f mm ss ff = showMmSsFf <$> fromMmSsFf mm ss ff- f 0 12 3 `shouldReturn` "00:12:03"- f 9 17 73 `shouldReturn` "09:17:73"- f 99 59 74 `shouldReturn` "99:59:74"- f 100 0 35 `shouldReturn` "100:00:35"+ f 0 12 3 `shouldReturn` "00:12:03"+ f 9 17 73 `shouldReturn` "09:17:73"+ f 99 59 74 `shouldReturn` "99:59:74"+ f 100 0 35 `shouldReturn` "100:00:35" describe "mkMcn" $ do context "when input value is a valid MCN" $ it "returns the MCN all right" $@@ -45,8 +43,8 @@ mkMcn (unMcn mcn) `shouldReturn` mcn context "when input value is invalid" $ it "throws the correct exception" $ do- mkMcn "something" `shouldThrow` (== InvalidMcn "something")- mkMcn "123123123" `shouldThrow` (== InvalidMcn "123123123")+ mkMcn "something" `shouldThrow` (== InvalidMcn "something")+ mkMcn "123123123" `shouldThrow` (== InvalidMcn "123123123") mkMcn "111222333444a" `shouldThrow` (== InvalidMcn "111222333444a") describe "mkCueText" $ do context "when input value is a valid CUE text" $@@ -74,6 +72,6 @@ mkIsrc (unIsrc isrc) `shouldReturn` isrc context "when input value is invalid" $ it "throws the correct exception" $ do- mkIsrc "a" `shouldThrow` (== InvalidIsrc "a")+ mkIsrc "a" `shouldThrow` (== InvalidIsrc "a") mkIsrc "12323asoent8" `shouldThrow` (== InvalidIsrc "12323asoent8") mkIsrc "aoe8u120997u" `shouldThrow` (== InvalidIsrc "aoe8u120997u")