@@ -25,29 +25,24 @@ where
25
25
import CLC.Stackage.Utils.Paths qualified as Paths
26
26
import Control.Applicative (Alternative ((<|>) ))
27
27
import Control.DeepSeq (NFData )
28
- import Control.Monad (void )
29
28
import Data.Aeson (FromJSON (parseJSON ), ToJSON (toJSON ))
30
29
import Data.Aeson qualified as Asn
31
30
import Data.Char qualified as Ch
32
31
import Data.String (IsString (fromString ))
33
32
import Data.Text (Text )
34
33
import Data.Text qualified as T
35
- import Data.Void (Void )
36
34
import GHC.Generics (Generic )
37
35
import System.OsPath (OsPath )
38
- import Text.Megaparsec qualified as MP
39
- import Text.Megaparsec.Char qualified as MPC
40
- import Text.Megaparsec.Char.Lexer qualified as MPCL
41
36
42
37
-- | Wrapper for package version.
43
38
data PackageVersion
44
39
= -- | Basic version text e.g. "2.3".
45
40
PackageVersionText Text
46
41
| -- | Represents an installed lib e.g. "foo installed" from cabal
47
- -- constraints. This is included in the from/toJSON instances, for the
42
+ -- constraints. This is included in the from/toJSON instances, for
48
43
-- writing/reading the report.
49
44
--
50
- -- Generally speaking, this is only used when clc-stackage is falls back
45
+ -- Generally speaking, this is only used when clc-stackage falls back
51
46
-- to the cabal.config endpoint, or is used with an explicit
52
47
-- --snapshot-path argument.
53
48
PackageVersionInstalled
@@ -120,49 +115,57 @@ toTextNoInstalled p = p.name <> versToTextNoInstalled p.version
120
115
-- - "aeson ==2.0.0,"
121
116
-- - "mtl installed"
122
117
fromCabalConstraintsText :: Text -> Maybe Package
123
- fromCabalConstraintsText txt =
124
- case MP. parse (MPC. space *> packageParser) " package" txt of
125
- Right x -> Just x
126
- Left _ -> Nothing
118
+ fromCabalConstraintsText = packageParser . T. stripStart
127
119
128
- type Parser = MP. Parsec Void Text
120
+ -- * Parser conventions: Following megaparsec's lead, each parser assumes
129
121
130
- -- | Parses packages e.g.
122
+ -- it does not start with whitespace, but consumes all trailing
123
+ -- whitespace. Hence when we return (Parsed, Rest), we need to strip
124
+ -- leading whitespace from Rest, so that the next parse can follow the same
125
+ -- assumption.
131
126
--
132
- -- - "aeson ==2.0.0,"
133
- -- - "mtl installed"
134
- packageParser :: Parser Package
135
- packageParser = do
136
- name <- nameParser
137
- vers <- versionTextParser <|> versionInstalledParser
127
+ -- NOTE: [*Parsers]
128
+ --
129
+ -- DIY parser, where each function parses only as much as it needs, then
130
+ -- returns the rest to be fed into the next parser. Following megaparsec's
131
+ -- lead, each parser assumes that it is at the start of relevant text
132
+ -- (i.e. no leading whitespace), and consumes trailing whitespace.
133
+ --
134
+ -- Hence the "rest" that is returned must have its leading whitespace stripped,
135
+ -- so that the next parse can make the same assumption.
136
+
137
+ packageParser :: Text -> Maybe Package
138
+ packageParser txt = do
139
+ (name, r1) <- nameParser txt
140
+ (vers, _) <- versionTextParser r1 <|> versionInstalledParser r1
138
141
pure $ MkPackage name vers
139
142
140
- nameParser :: Parser Text
141
- nameParser = lexeme $ MP. takeWhile1P (Just " name" ) isNameChar
143
+ -- Split on whitepspace or equals e.g. "mtl installed", "aeson ==1.2.3".
144
+ nameParser :: Text -> Maybe (Text , Text )
145
+ nameParser txt
146
+ | T. null name = Nothing
147
+ | otherwise = Just (name, T. stripStart rest)
142
148
where
143
- isNameChar c = c /= ' ' && c /= ' ='
144
-
145
- versionInstalledParser :: Parser PackageVersion
146
- versionInstalledParser = do
147
- MPC. string " installed"
148
- mcommaParser
149
- pure PackageVersionInstalled
150
-
151
- versionTextParser :: Parser PackageVersion
152
- versionTextParser = do
153
- lexeme $ MPC. string delim
154
- vers <- lexeme $ MP. takeWhile1P (Just " version" ) isVersChar
155
- mcommaParser
156
- pure $ PackageVersionText vers
149
+ (name, rest) = T. break isNameChar txt
150
+ isNameChar c = c == ' ' || c == ' ='
151
+
152
+ -- Parse "installed".
153
+ versionInstalledParser :: Text -> Maybe (PackageVersion , Text )
154
+ versionInstalledParser txt = do
155
+ rest <- T. stripPrefix " installed" txt
156
+ pure (PackageVersionInstalled , T. stripStart rest)
157
+
158
+ -- Parse e.g. "==1.2.3".
159
+ versionTextParser :: Text -> Maybe (PackageVersion , Text )
160
+ versionTextParser txt = do
161
+ r1 <- T. stripPrefix delim txt
162
+ let (vers, r2) = T. span isVersChar (T. stripStart r1)
163
+ if not (T. null vers)
164
+ then Just (PackageVersionText vers, T. stripStart r2)
165
+ else Nothing
157
166
where
158
167
isVersChar c = Ch. isDigit c || c == ' .'
159
168
160
- mcommaParser :: Parser ()
161
- mcommaParser = lexeme $ void $ MP. optional $ MPC. char ' ,'
162
-
163
- lexeme :: Parser a -> Parser a
164
- lexeme = MPCL. lexeme MPC. space
165
-
166
169
versToTextInstalled :: PackageVersion -> Text
167
170
versToTextInstalled (PackageVersionText t) = " " <> delim <> t
168
171
versToTextInstalled PackageVersionInstalled = " installed"
0 commit comments