Skip to content

Commit dfa920c

Browse files
committedAug 5, 2023
Switch to attoparsec for gentoo scan
Signed-off-by: hololeap <hololeap@users.noreply.github.com>
1 parent fc9b63f commit dfa920c

File tree

3 files changed

+74
-35
lines changed

3 files changed

+74
-35
lines changed
 

‎ShellCheck.cabal

+5-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ library
6666
directory >= 1.2.3 && < 1.4,
6767

6868
-- When cabal supports it, move this to setup-depends:
69-
process
69+
process,
70+
71+
-- support for scanning Gentoo eclasses
72+
attoparsec,
73+
text
7074
exposed-modules:
7175
ShellCheck.AST
7276
ShellCheck.ASTLib

‎src/ShellCheck/Data.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Use:
2525
import Paths_ShellCheck (version)
2626
shellcheckVersion = showVersion version -- VERSIONSTRING
2727

28-
28+
genericInternalVariables :: [String]
2929
genericInternalVariables = [
3030
-- Generic
3131
"", "_", "rest", "REST",
@@ -153,7 +153,7 @@ eclassVarsFromMap :: EclassMap -> String -> [String]
153153
eclassVarsFromMap gMap eclass =
154154
Data.Map.findWithDefault []
155155
eclass
156-
gMap
156+
(Data.Map.map (map decodeLenient) gMap)
157157

158158
portageInternalVariables :: [String] -> EclassMap -> [String]
159159
portageInternalVariables inheritedEclasses gMap =

‎src/ShellCheck/PortageVariables.hs

+67-32
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
{-# LANGUAGE ApplicativeDo #-}
2+
{-# LANGUAGE LambdaCase #-}
3+
{-# LANGUAGE OverloadedStrings #-}
4+
{-# LANGUAGE TupleSections #-}
15

26
module ShellCheck.PortageVariables
37
( RepoName
@@ -9,25 +13,37 @@ module ShellCheck.PortageVariables
913
, Eclass(..)
1014
, portageVariables
1115
, scanRepos
16+
, decodeLenient
1217
) where
1318

1419
import Control.Applicative
20+
import Control.Exception (bracket)
1521
import Control.Monad
16-
import Control.Monad.Trans.Class
22+
import Control.Monad.Trans.Class (lift)
1723
import Control.Monad.Trans.Maybe
18-
import Data.Map (Map)
24+
import Data.Attoparsec.ByteString
25+
import qualified Data.Attoparsec.ByteString as A
26+
import Data.Attoparsec.ByteString.Char8 hiding (takeWhile)
27+
import Data.ByteString (ByteString)
28+
import qualified Data.ByteString as B
29+
import Data.Char (ord)
1930
import qualified Data.Map as M
31+
import Data.Maybe (fromJust)
32+
import qualified Data.Text as T
33+
import qualified Data.Text.Encoding as T
34+
import qualified Data.Text.Encoding.Error as T
2035
import System.Directory (listDirectory)
2136
import System.Exit (ExitCode(..))
2237
import System.FilePath
38+
import System.IO (hClose)
2339
import System.Process
24-
import Text.Parsec hiding ((<|>))
25-
import Text.Parsec.String
2640

27-
type RepoName = String
28-
type RepoPath = FilePath
41+
import Prelude hiding (takeWhile)
42+
43+
type RepoName = ByteString
44+
type RepoPath = ByteString
2945
type EclassName = String
30-
type EclassVar = String
46+
type EclassVar = ByteString
3147

3248
-- | This is used for looking up what eclass variables are inherited,
3349
-- keyed by the name of the eclass.
@@ -57,7 +73,7 @@ scanRepos = do
5773
let cmd = "/usr/bin/portageq"
5874
let args = ["repos_config", "/"]
5975
out <- runOrDie cmd args
60-
case parse reposParser "scanRepos" out of
76+
case parseOnly reposParser out of
6177
Left pe -> fail $ show pe
6278
Right nps -> do
6379
forM nps $ \(n,p) -> Repository n p <$> getEclasses p
@@ -67,37 +83,39 @@ scanRepos = do
6783
reposParser :: Parser [(RepoName, RepoPath)]
6884
reposParser =
6985
choice
70-
[ [] <$ eof
86+
[ [] <$ endOfInput
7187
, repoName >>= repoBlock
7288
]
7389
where
7490
-- Get the name of the repo at the top of the block
7591
repoName :: Parser RepoName
76-
repoName
77-
= char '['
78-
*> manyTill anyChar (try (char ']'))
79-
<* endOfLine
92+
repoName = do
93+
_ <- char '['
94+
n <- takeWhile (/= fromIntegral (ord ']'))
95+
_ <- char ']'
96+
_ <- endOfLine
97+
pure n
8098

8199
-- Parse the block for location field
82100
repoBlock :: RepoName -> Parser [(RepoName, RepoPath)]
83101
repoBlock n = choice
84-
[ try $ do
85-
l <- string "location = " *> takeLine
102+
[ do
103+
l <- "location = " *> takeLine
86104
-- Found the location, skip the rest of the block
87105
skipMany miscLine *> endOfBlock
88106
insert (n,l)
89107
-- Did not find the location, keep trying
90-
, try $ miscLine *> repoBlock n
108+
, miscLine *> repoBlock n
91109
-- Reached the end of the block, no location field
92110
, endOfBlock *> ignore
93111
]
94112

95113
miscLine :: Parser ()
96114
miscLine = skipNonEmptyLine
97115

98-
-- A block ends with an eol or eof
116+
-- A block either ends with an empty line or eof
99117
endOfBlock :: Parser ()
100-
endOfBlock = void endOfLine <|> eof
118+
endOfBlock = endOfLine <|> endOfInput
101119

102120
-- cons the repo and continue parsing
103121
insert :: (RepoName, RepoPath) -> Parser [(RepoName, RepoPath)]
@@ -114,7 +132,7 @@ reposParser =
114132
-- repo.
115133
getEclasses :: RepoPath -> IO [Eclass]
116134
getEclasses repoLoc = fmap (maybe [] id) $ runMaybeT $ do
117-
let eclassDir = repoLoc </> "eclass"
135+
let eclassDir = (decodeLenient repoLoc) </> "eclass"
118136

119137
-- Silently fail if the repo doesn't have an eclass dir
120138
fs <- MaybeT $ Just <$> listDirectory eclassDir <|> pure Nothing
@@ -131,40 +149,57 @@ getEclasses repoLoc = fmap (maybe [] id) $ runMaybeT $ do
131149
eclassParser :: Parser [EclassVar]
132150
eclassParser = choice
133151
[ -- cons the EclassVar to the list and continue
134-
try $ liftA2 (:) eclassVar eclassParser
152+
liftA2 (:) eclassVar eclassParser
135153
-- or skip the line and continue
136154
, skipLine *> eclassParser
137155
-- or end the list on eof
138-
, [] <$ eof
156+
, [] <$ endOfInput
139157
]
140158
where
141159
-- Scans for @ECLASS_VARIABLE comments rather than parsing the raw bash
142160
eclassVar :: Parser EclassVar
143-
eclassVar = string "# @ECLASS_VARIABLE: " *> takeLine
161+
eclassVar = "# @ECLASS_VARIABLE: " *> takeLine
144162

145-
takeLine :: Parser String
146-
takeLine = manyTill anyChar (try endOfLine)
163+
takeLine :: Parser ByteString
164+
takeLine = A.takeWhile (not . isEndOfLine) <* endOfLine
147165

148166
-- | Fails if next char is 'endOfLine'
149167
skipNonEmptyLine :: Parser ()
150-
skipNonEmptyLine = notFollowedBy endOfLine *> skipLine
168+
skipNonEmptyLine = A.satisfy (not . isEndOfLine) *> skipLine
151169

152170
skipLine :: Parser ()
153-
skipLine = void takeLine
171+
skipLine = A.skipWhile (not . isEndOfLine) <* endOfLine
172+
173+
parseFromFile :: Parser a -> FilePath -> IO (Either String a)
174+
parseFromFile p = fmap (parseOnly p) . B.readFile
154175

155176
-- | Run the command and return the full stdout string (stdin is ignored).
156177
--
157178
-- If the command exits with a non-zero exit code, this will throw an
158179
-- error including the captured contents of stdout and stderr.
159-
runOrDie :: FilePath -> [String] -> IO String
160-
runOrDie cmd args = do
161-
(ec, o, e) <- readProcessWithExitCode cmd args ""
180+
runOrDie :: FilePath -> [String] -> IO ByteString
181+
runOrDie cmd args = bracket acquire release $ \(_,o,e,p) -> do
182+
ot <- B.hGetContents (fromJust o)
183+
et <- B.hGetContents (fromJust e)
184+
ec <- waitForProcess p
162185
case ec of
163-
ExitSuccess -> pure o
186+
ExitSuccess -> pure ot
164187
ExitFailure i -> fail $ unlines $ map unwords
165188
$ [ [ show cmd ]
166189
++ map show args
167190
++ [ "failed with exit code", show i]
168-
, [ "stdout:" ], [ o ]
169-
, [ "stderr:" ], [ e ]
191+
, [ "stdout:" ], [ decodeLenient ot ]
192+
, [ "stderr:" ], [ decodeLenient et ]
170193
]
194+
where
195+
acquire = createProcess (proc cmd args)
196+
{ std_in = NoStream
197+
, std_out = CreatePipe
198+
, std_err = CreatePipe
199+
}
200+
release (i,o,e,p) = do
201+
_ <- waitForProcess p
202+
forM_ [i,o,e] $ mapM_ hClose
203+
204+
decodeLenient :: ByteString -> String
205+
decodeLenient = T.unpack . T.decodeUtf8With T.lenientDecode

0 commit comments

Comments
 (0)
Please sign in to comment.