{-| Vaším úkolem je naprogramovat zjednodušenou verzi unixového příkazu which. Vytvořte tedy binárku, která pro každý argument z příkazové řádky zjistí, jestli se jedná o spustitelný soubor nacházející se někde v automaticky prohledávaných cestách (proměnná PATH). Jestli ano, vypište celou cestu k této binárce. Několik poznámek: * Kompilovaný program začíná běh ve funkci main :: IO (). * Použijte funkce z modulů System.Directory, System.Environment a System.FilePath. * Doporučení: Raději než odchytávat výjimky kontrolujte existenci souboru předem (i když to vytváří race condition). * Pokud nebude zadán žádný argument, vypište nápovědu. * Vypisujte cestu pouze pro první nalezený spustitelný soubor daného jména. -} module Main (main) where import System.Directory import System.FilePath import System.Environment import Data.List import System.IO ( hPutStrLn, stderr ) import Control.Exception ( handle, IOException ) import Control.Monad ( forM_, filterM ) main = handle failure $ do args <- getArgs case parse args of Nothing -> help Just (listAll, what) -> which listAll what which :: Bool -> [String] -> IO () which listAll what = do path <- getEnv "PATH" let dirs = splitSearchPath path forM_ what $ \w -> do eps <- execPaths dirs w let toPrint = if listAll then eps else take 1 eps mapM_ putStrLn toPrint execPaths :: [FilePath] -> String -> IO [FilePath] execPaths path what = filterM isExecutable $ map ( what) path isExecutable :: FilePath -> IO Bool isExecutable path = do exists <- doesFileExist path if exists then do perm <- getPermissions path return $ executable perm else return False help :: IO () help = do name <- getProgName hPutStrLn stderr $ unlines [ "usage: " ++ name ++ " [-a | --all] [-h | --help] program_name [...]", "", " find given program name (or multiple program names) in path", "", " --all, -a list all mathing executables, not just first one", " --help, -h print this help" ] failure :: IOException -> IO () failure ex = hPutStrLn stderr $ "Fatal error: " ++ show ex parse :: [String] -> Maybe (Bool, [String]) parse args | hasOpt 'h' "help" args = Nothing | not . null $ noOpts args = Just (hasOpt 'a' "all" args, noOpts args) | otherwise = Nothing hasOpt :: Char -> String -> [String] -> Bool hasOpt short_ long_ = any (\x -> x == short || x == long) where short = ['-', short_] long = '-':'-':long_ noOpts :: [String] -> [String] noOpts = filter ((/= '-') . head) . filter (not . null)