Skip to content

Commit 8d80065

Browse files
committed
Fix cabal layout: move Main.hs to app/, derive Eq/Show on lexer types.
The previous layout had the library and the executable sharing `hs-source-dirs: ./`, which let GHC's home-module rule treat every library module as a home source of the executable. It then tried to recompile them against the executable's (much smaller) dependency list and failed on clock/array/hscurses. Putting Main.hs under `app/` gives the executable a non-overlapping source directory and lets the library do its job: compiled once, linked into both the executable and the test-suite. Lexer-relevant types in `Syntax` now derive `Eq` and `Show`, which lets `LexerSpec` compare whole `Msg` values directly instead of pattern- matching out individual fields. The tests get noticeably shorter.
1 parent 6d528a8 commit 8d80065

4 files changed

Lines changed: 36 additions & 66 deletions

File tree

Syntax.hs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,20 @@ instance Pretty Quit where
6666

6767
-- mpg123 tagline. Output at startup.
6868
data Tag = Tag
69+
deriving stock (Eq, Show)
6970

7071
-- Track info if ID fields are in the file, otherwise file name.
7172
newtype File = File (Either ByteString Id3)
73+
deriving stock (Eq, Show)
7274

73-
-- ID3 info
75+
-- ID3 info
7476
data Id3 = Id3
7577
{ id3title :: !ByteString
7678
, id3artist :: !ByteString
7779
, id3album :: !ByteString
7880
, id3str :: !ByteString
7981
}
82+
deriving stock (Eq, Show)
8083

8184
-- , year :: Maybe ByteString
8285
-- , genre :: Maybe ByteString }
@@ -111,8 +114,9 @@ newtype Info = Info {
111114
-- checksummed :: !Bool,
112115
-- emphasis :: !Int,
113116
-- bitrate :: !Int,
114-
-- extension :: !Int
117+
-- extension :: !Int
115118
}
119+
deriving stock (Eq, Show)
116120

117121
-- @F <current-frame> <frames-remaining> <current-time> <time-remaining>
118122
-- Frame decoding status updates (once per frame).
@@ -128,6 +132,7 @@ data Frame = Frame {
128132
currentTime :: !(Fixed E2),
129133
timeLeft :: !(Fixed E2)
130134
}
135+
deriving stock (Eq, Show)
131136

132137
-- @P {0, 1, 2}
133138
-- Stop/pause status.
@@ -158,3 +163,4 @@ data Msg = T {-# UNPACK #-} !Tag
158163
| I {-# UNPACK #-} !Info
159164
| R {-# UNPACK #-} !Frame
160165
| S !Status
166+
deriving stock (Eq, Show)
File renamed without changes.

hmp3-ng.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ library
8080
executable hmp3
8181
import: opts
8282
main-is: Main.hs
83-
hs-source-dirs: ./
83+
hs-source-dirs: app
8484
ghc-options: -threaded
8585
build-depends:
8686
, base

test/LexerSpec.hs

Lines changed: 27 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -11,85 +11,49 @@ import Syntax
1111
tests :: TestTree
1212
tests = testGroup "Lexer"
1313
[ testGroup "doP (status messages)"
14-
[ testCase "0 is Stopped" $ status (doP "0") @?= Stopped
15-
, testCase "1 is Paused" $ status (doP "1") @?= Paused
16-
, testCase "2 is Playing" $ status (doP "2") @?= Playing
17-
, testCase "3 is Playing" $ status (doP "3") @?= Playing
18-
, testCase "empty is Playing" $ status (doP "") @?= Playing
19-
, testCase "garbage is Playing" $ status (doP "xyz") @?= Playing
14+
[ testCase "0 is Stopped" $ doP "0" @?= S Stopped
15+
, testCase "1 is Paused" $ doP "1" @?= S Paused
16+
, testCase "2 is Playing" $ doP "2" @?= S Playing
17+
, testCase "3 is Playing" $ doP "3" @?= S Playing
18+
, testCase "empty is Playing" $ doP "" @?= S Playing
19+
, testCase "garbage is Playing" $ doP "xyz" @?= S Playing
2020
]
2121
, testGroup "doF (frame messages)"
22-
[ testCase "all four fields parse" $ do
23-
let Frame{..} = frame (doF "123 456 12.34 56.78")
24-
currentFrame @?= 123
25-
framesLeft @?= 456
26-
currentTime @?= 12.34
27-
timeLeft @?= 56.78
28-
, testCase "negative timeLeft is clamped to zero" $ do
29-
timeLeft (frame (doF "0 0 0.00 -1.00")) @?= 0
22+
[ testCase "all four fields parse" $
23+
doF "123 456 12.34 56.78" @?= R (Frame 123 456 12.34 56.78)
24+
, testCase "negative timeLeft is clamped to zero" $
25+
doF "0 0 0.00 -1.00" @?= R (Frame 0 0 0.00 0)
3026
]
3127
, testGroup "doS (info messages)"
3228
[ testCase "userinfo combines version, bitrate, kHz" $
33-
userinfo (info (doS "1.0 1 44100 stereo 0 0 2 0 0 0 128 0"))
34-
@?= "mpeg 1.0 128kbit/s 44kHz"
29+
doS "1.0 1 44100 stereo 0 0 2 0 0 0 128 0"
30+
@?= I (Info "mpeg 1.0 128kbit/s 44kHz")
3531
]
3632
, testGroup "doI (track info / id3)"
3733
[ testCase "non-id3 input becomes Left filename" $
38-
fileLeft (doI "song.mp3") @?= "song.mp3"
34+
doI "song.mp3"
35+
@?= F (File (Left "song.mp3"))
3936
, testCase "non-id3 input is trimmed" $
40-
fileLeft (doI " song.mp3 ") @?= "song.mp3"
41-
, testCase "id3 with title only" $ do
42-
let i = fileRight (doI ("ID3:" <> field30 "Title"))
43-
id3title i @?= "Title"
44-
id3artist i @?= ""
45-
id3album i @?= ""
46-
id3str i @?= "Title"
47-
, testCase "id3 with title and artist" $ do
48-
let i = fileRight (doI ("ID3:" <> field30 "Title" <> field30 "Artist"))
49-
id3title i @?= "Title"
50-
id3artist i @?= "Artist"
51-
id3album i @?= ""
52-
id3str i @?= "Artist : Title"
53-
, testCase "id3 with title, artist, and album" $ do
54-
let i = fileRight (doI ("ID3:" <> field30 "Title"
55-
<> field30 "Artist"
56-
<> field30 "Album"))
57-
id3title i @?= "Title"
58-
id3artist i @?= "Artist"
59-
id3album i @?= "Album"
60-
id3str i @?= "Artist : Album : Title"
37+
doI " song.mp3 "
38+
@?= F (File (Left "song.mp3"))
39+
, testCase "id3 with title only" $
40+
doI ("ID3:" <> field30 "Title")
41+
@?= F (File (Right (Id3 "Title" "" "" "Title")))
42+
, testCase "id3 with title and artist" $
43+
doI ("ID3:" <> field30 "Title" <> field30 "Artist")
44+
@?= F (File (Right (Id3 "Title" "Artist" "" "Artist : Title")))
45+
, testCase "id3 with title, artist, and album" $
46+
doI ("ID3:" <> field30 "Title" <> field30 "Artist" <> field30 "Album")
47+
@?= F (File (Right (Id3 "Title" "Artist" "Album" "Artist : Album : Title")))
6148
, testCase "id3 with empty title falls back to Left of trimmed input" $
6249
-- mpg123 sometimes returns ID3 records with a blank title; rather than
6350
-- present an empty track name, the parser exposes the raw line.
64-
fileLeft (doI ("ID3:" <> field30 "" <> field30 "Artist"))
65-
@?= "ID3:" <> P.replicate 30 ' ' <> "Artist"
51+
doI ("ID3:" <> field30 "" <> field30 "Artist")
52+
@?= F (File (Left ("ID3:" <> P.replicate 30 ' ' <> "Artist")))
6653
]
6754
]
6855

69-
------------------------------------------------------------------------
70-
-- Helpers
71-
7256
-- Pad/truncate a ByteString to exactly 30 characters with trailing spaces,
7357
-- matching the fixed-width field convention used by mpg123 ID3 output.
7458
field30 :: P.ByteString -> P.ByteString
7559
field30 b = P.take 30 (b <> P.replicate 30 ' ')
76-
77-
status :: Msg -> Status
78-
status (S s) = s
79-
status _ = error "expected S"
80-
81-
frame :: Msg -> Frame
82-
frame (R f) = f
83-
frame _ = error "expected R"
84-
85-
info :: Msg -> Info
86-
info (I i) = i
87-
info _ = error "expected I"
88-
89-
fileLeft :: Msg -> P.ByteString
90-
fileLeft (F (File (Left s))) = s
91-
fileLeft _ = error "expected F (File (Left _))"
92-
93-
fileRight :: Msg -> Id3
94-
fileRight (F (File (Right i))) = i
95-
fileRight _ = error "expected F (File (Right _))"

0 commit comments

Comments
 (0)