From af46758ff17dab17d23a1f30b92a0c470bc53f29 Mon Sep 17 00:00:00 2001 From: Pontus Andersson <epontan@gmail.com> Date: Mon, 22 Apr 2019 14:34:38 +0200 Subject: [PATCH] Add option to look for sources in alternate root paths Add a new optional flag "-r|--root ROOTPATHS", where ROOTPATHS is a colon separated list of paths, that will look for external sources in alternate roots. This is particular useful when the run-time environment does not fully match the development environment. The #shellcheck source=file directive is useful, but has its limitations in certain scenarios. Also, in many cases the directive could be removed from scripts when the root flag is used. Script example.bash: #!/bin/bash source /etc/foo/config Example usage where etc/foo/config exists in skel/foo: # shellcheck -x -r skel/foo:skel/core example.bash --- shellcheck.hs | 30 ++++++++++++++++++++++++++++++ src/ShellCheck/Interface.hs | 4 ++++ src/ShellCheck/Parser.hs | 4 +++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/shellcheck.hs b/shellcheck.hs index 06516e5..c57c379 100644 --- a/shellcheck.hs +++ b/shellcheck.hs @@ -69,6 +69,7 @@ instance Monoid Status where data Options = Options { checkSpec :: CheckSpec, externalSources :: Bool, + rootPaths :: [FilePath], formatterOptions :: FormatterOptions, minSeverity :: Severity } @@ -76,6 +77,7 @@ data Options = Options { defaultOptions = Options { checkSpec = emptyCheckSpec, externalSources = False, + rootPaths = [], formatterOptions = newFormatterOptions { foColorOption = ColorAuto }, @@ -98,6 +100,9 @@ options = [ "Output format (" ++ formatList ++ ")", Option "" ["norc"] (NoArg $ Flag "norc" "true") "Don't look for .shellcheckrc files", + Option "r" ["root"] + (ReqArg (Flag "root") "ROOTPATHS") + "Specify alternate root path(s) when looking for sources (colon separated)", Option "s" ["shell"] (ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (sh, bash, dash, ksh)", @@ -311,6 +316,12 @@ parseOption flag options = } } + Flag "root" str -> do + let paths = filter (not . null) $ split ':' str + return options { + rootPaths = paths + } + Flag "sourced" _ -> return options { checkSpec = (checkSpec options) { @@ -362,8 +373,10 @@ ioInterface options files = do inputs <- mapM normalize files cache <- newIORef emptyCache configCache <- newIORef ("", Nothing) + let rootPathsCache = rootPaths options return SystemInterface { siReadFile = get cache inputs, + siFindSource = findSourceFile rootPathsCache, siGetConfig = getConfig configCache } where @@ -455,6 +468,23 @@ ioInterface options files = do putStrLn $ file ++ ": " ++ show err return ("", True) + findSourceFile rootPaths file = do + case file of + ('/':root) -> do + source <- find root + return source + _ -> + return file + where + find root = do + sources <- filterM doesFileExist paths + case sources of + [] -> return file + (first:_) -> return first + where + paths = map join rootPaths + join path = joinPath [path, root] + inputFile file = do (handle, shouldCache) <- if file == "-" diff --git a/src/ShellCheck/Interface.hs b/src/ShellCheck/Interface.hs index 0661386..8b0ba0f 100644 --- a/src/ShellCheck/Interface.hs +++ b/src/ShellCheck/Interface.hs @@ -73,6 +73,8 @@ import qualified Data.Map as Map data SystemInterface m = SystemInterface { -- Read a file by filename, or return an error siReadFile :: String -> m (Either ErrorMessage String), + -- Find source file in alternate root paths + siFindSource :: String -> m (FilePath), -- Get the configuration file (name, contents) for a filename siGetConfig :: String -> m (Maybe (FilePath, String)) } @@ -287,6 +289,7 @@ data ColorOption = mockedSystemInterface :: [(String, String)] -> SystemInterface Identity mockedSystemInterface files = SystemInterface { siReadFile = rf, + siFindSource = fs, siGetConfig = const $ return Nothing } where @@ -294,6 +297,7 @@ mockedSystemInterface files = SystemInterface { case filter ((== file) . fst) files of [] -> return $ Left "File not included in mock." [(_, contents)] -> return $ Right contents + fs file = return file mockRcFile rcfile mock = mock { siGetConfig = const . return $ Just (".shellcheckrc", rcfile) diff --git a/src/ShellCheck/Parser.hs b/src/ShellCheck/Parser.hs index 4ecf602..2abdb36 100644 --- a/src/ShellCheck/Parser.hs +++ b/src/ShellCheck/Parser.hs @@ -2087,7 +2087,9 @@ readSource t@(T_Redirecting _ _ (T_SimpleCommand cmdId _ (cmd:file':rest'))) = d input <- if filename == "/dev/null" -- always allow /dev/null then return (Right "") - else system $ siReadFile sys filename + else do + filename' <- system $ siFindSource sys filename + system $ siReadFile sys filename' case input of Left err -> do parseNoteAtId (getId file) InfoC 1091 $