1+ {-#LANGUAGE ApplicativeDo #-}
2+ {-#LANGUAGE LambdaCase #-}
3+ {-#LANGUAGE OverloadedStrings #-}
4+ {-#LANGUAGE TupleSections #-}
15
26module ShellCheck.PortageVariables
37 (RepoName
@@ -9,25 +13,37 @@ module ShellCheck.PortageVariables
913 ,Eclass (.. )
1014 ,portageVariables
1115 ,scanRepos
16+ ,decodeLenient
1217 )where
1318
1419import Control.Applicative
20+ import Control.Exception (bracket )
1521import Control.Monad
16- import Control.Monad.Trans.Class
22+ import Control.Monad.Trans.Class ( lift )
1723import 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 )
1930import 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
2035import System.Directory (listDirectory )
2136import System.Exit (ExitCode (.. ))
2237import System.FilePath
38+ import System.IO (hClose )
2339import 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
2945type 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
5773let cmd= " /usr/bin/portageq"
5874let args= [" repos_config" ," /" ]
5975 out<- runOrDie cmd args
60- case parse reposParser" scanRepos " outof
76+ case parseOnly reposParser outof
6177Left pe-> fail $ show pe
6278Right nps-> do
6379 forM nps$ \ (n,p)-> Repository n p<$> getEclasses p
@@ -67,37 +83,39 @@ scanRepos = do
6783reposParser :: Parser [(RepoName ,RepoPath )]
6884reposParser=
6985 choice
70- [[] <$ eof
86+ [[] <$ endOfInput
7187 , repoName>>= repoBlock
7288 ]
7389where
7490-- Get the name of the repo at the top of the block
7591repoName :: 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
82100repoBlock :: 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
95113miscLine :: Parser ()
96114 miscLine= skipNonEmptyLine
97115
98- -- A block ends with aneol or eof
116+ -- A blockeither ends with anempty line or eof
99117endOfBlock :: Parser ()
100- endOfBlock= void endOfLine<|> eof
118+ endOfBlock= endOfLine<|> endOfInput
101119
102120-- cons the repo and continue parsing
103121insert :: (RepoName ,RepoPath )-> Parser [(RepoName ,RepoPath )]
@@ -114,7 +132,7 @@ reposParser =
114132-- repo.
115133getEclasses :: RepoPath -> IO [Eclass ]
116134getEclasses 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
131149eclassParser :: Parser [EclassVar ]
132150eclassParser= 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 ]
140158where
141159-- Scans for @ECLASS_VARIABLE comments rather than parsing the raw bash
142160eclassVar :: 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'
149167skipNonEmptyLine :: Parser ()
150- skipNonEmptyLine= notFollowedBy endOfLine *> skipLine
168+ skipNonEmptyLine= A. satisfy ( not . isEndOfLine) *> skipLine
151169
152170skipLine :: 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
162185case ecof
163- ExitSuccess -> pure o
186+ ExitSuccess -> pure ot
164187ExitFailure 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. decodeUtf8WithT. lenientDecode