packages feed

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 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 @@ [![Hackage](https://img.shields.io/hackage/v/cue-sheet.svg?style=flat)](https://hackage.haskell.org/package/cue-sheet) [![Stackage Nightly](http://stackage.org/package/cue-sheet/badge/nightly)](http://stackage.org/nightly/package/cue-sheet) [![Stackage LTS](http://stackage.org/package/cue-sheet/badge/lts)](http://stackage.org/lts/package/cue-sheet)-[![Build Status](https://travis-ci.org/mrkkrp/cue-sheet.svg?branch=master)](https://travis-ci.org/mrkkrp/cue-sheet)+![CI](https://github.com/mrkkrp/cue-sheet/workflows/CI/badge.svg?branch=master) -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")