-- MCM - Machine Configuration Manager; manages the contents of files and directories -- Copyright (c) 2013-2016 Anthony Doggett -- -- Licence: -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . -- Parses all .mcm files below the current directory -- (If directories given as arguments, uses those instead) -- Writes the results to "tags" file (in current directory) -- Prints warnings for any files that fail to parse module Main (main) where import Parser (mcmParse) import ParserTypes (Define(..), Section(..), MCMFile(..), DefName(..)) import Paths_mcm (version) import Control.Monad (filterM, unless) import Data.Char (isAsciiUpper) import Data.List (isSuffixOf, intercalate, sort, foldl') import Data.Version (showVersion) import qualified Data.Map as Map import qualified Data.Text.Lazy as T import qualified Data.Text.Lazy.IO as TextIO import System.Console.GetOpt import System.Directory (getDirectoryContents) import System.Environment (getArgs) import System.Exit (exitSuccess) import System.FilePath (joinPath) import System.Posix.Files (getFileStatus, isDirectory) usage :: String usage = unlines ["Usage: mcmtags [DIR..]" ,"Parse all *.mcm files below the current directory," ,"or if directories given as arguments, below those directories." ,"The resulting tags are written to ./tags." ,"Print warnings for any files that fail to parse." ] toWarn :: FilePath -> String toWarn f = "WARNING: failed to parse " ++ f tryParse :: FilePath -> IO (Maybe MCMFile) tryParse f = do ss <- TextIO.readFile f case mcmParse ss of Right a -> return $ Just a Left _ -> return Nothing makeTag :: FilePath -> String -> String -> String makeTag f n ex_cmd = n ++ "\t" ++ f ++ "\t" ++ ex_cmd toTags :: (FilePath, MCMFile) -> [String] toTags (f, MCMFile pp (Section _ _ ds)) = ppTag:map dsTag (Map.elems ds) where ppTag = makeTag f (lastBitOfPath (show pp)) "/^MCM/" dsTag d = makeTag f (strDefName d) ("/^define " ++ strDefName d ++ "(/") lastBitOfPath :: String -> String lastBitOfPath = reverse . takeWhile (/= '.') . reverse strDefName = T.unpack . fromDefName . defName isDirectory' :: FilePath -> IO Bool isDirectory' f = do s <- getFileStatus f return $ isDirectory s findMcm :: FilePath -> IO [FilePath] findMcm p = do allfiles <- getDirectoryContents p let fs = [f | f <- allfiles, f `notElem` [".", ".."]] ms = [joinPath [p,f] | f <- fs, ".mcm" `isSuffixOf` f] ds = [joinPath [p,f] | f <- fs, isAsciiUpper (head f), '.' `notElem` f] dirs <- filterM isDirectory' ds children <- mapM findMcm dirs return $ ms ++ concat children splitParsed :: [(FilePath, Maybe MCMFile)] -> ([FilePath], [(FilePath, MCMFile)]) splitParsed [] = ([], []) splitParsed ((fp, m):xs) = let (as, bs) = splitParsed xs in case m of Nothing -> (fp:as, bs) Just f -> (as, (fp, f):bs) main :: IO () main = do args <- getArgs let (actions, nonOpts, msgs) = getOpt Permute options args unless (null msgs) $ error $ concat msgs ++ usageInfo usage options _ <- foldl' (>>=) (return defaultOptions) actions let toRecurse = case nonOpts of [] -> ["."] _ -> args toParse <- mapM findMcm toRecurse let toParse' = concat toParse parsed <- mapM tryParse toParse' let fparsed = zip toParse' parsed let (bad, good) = splitParsed fparsed tags = sort $ concatMap toTags good mapM_ (putStrLn . toWarn) bad writeFile "tags" $ intercalate "\n" tags return () data Options = Options {} defaultOptions :: Options defaultOptions = Options {} options :: [OptDescr (Options -> IO Options)] options = [Option "V" ["version"] (NoArg displayVersion) "show version and exit" ,Option "h" ["help"] (NoArg justHelp) "show this help and exit" ] displayVersion :: Options -> IO Options displayVersion _ = do putStrLn $ "mcmtags " ++ showVersion version exitSuccess justHelp :: Options -> IO Options justHelp _ = do putStrLn $ usageInfo usage options exitSuccess