{-# LANGUAGE CPP #-} -- | -- Module : System.Terminfo -- Copyright : (c) Bryan Richter (2013) -- License : BSD-style -- Maintainer : bryan.richter@gmail.com -- -- This is a pure-Haskell (no FFI) module for accessing terminfo databases, -- which contain characteristics, or capabilities, for the various -- terminals such as screen, vt100, or xterm. Among other things, the -- capabilities include the idiosyncratic character sequences needed to -- send commands to the terminal. These commands include things like cursor -- movement. -- -- For a deeper understanding of terminfo, consult the man pages for -- term(5) and terminfo(5). -- -- There are three parts to this module: acquiring a terminfo database, -- querying the database, and defining the capabilities. -- -- This module is dead simple, so a single example will hopefully suffice -- to demonstrate its usage. -- -- @ -- import System.Terminfo -- import System.Terminfo.Caps as C -- uglyExample :: IO (Maybe Int) -- uglyExample = do -- term \<- fromJust \<$> lookupEnv \"TERM\" -- db \<- 'acquireDatabase' term -- let maxColors (Right d) = 'queryNumTermCap' d C.'MaxColors' -- return $ maxColors db -- @ -- -- >>> uglyExample -- Just 256 -- module System.Terminfo ( -- * Acquiring a Database acquireDatabase -- * Querying Capabilities -- $queryFuncs , queryBoolTermCap , queryNumTermCap , queryStrTermCap -- * The Capabilities -- $capabilities -- * The Database Type , TIDatabase ) where #if MIN_VERSION_base(4,8,0) import Control.Applicative ((<|>)) #else import Control.Applicative ((<$>), (<|>)) #endif import Control.Error import Control.Monad ((<=<), filterM) import qualified Data.ByteString as B import qualified Data.Map.Lazy as M import System.Directory import System.FilePath import System.IO import System.Terminfo.Types import System.Terminfo.DBParse import System.Terminfo.Internal (terminfoDBLocs) import System.Terminfo.Caps #if MIN_VERSION_errors(2,0,0) except :: m (Either e a) -> ExceptT e m a except = ExceptT #else type ExceptT = EitherT runExceptT :: EitherT e m a -> m (Either e a) runExceptT = runEitherT except :: m (Either e a) -> EitherT e m a except = EitherT #endif data DBType = BerkeleyDB | DirTreeDB deriving(Show) -- Old MacDonald had a farm... type EIO = ExceptT String IO acquireDatabase :: String -- ^ System name -> IO (Either String TIDatabase) -- ^ A database object for the terminal, if it exists. acquireDatabase = runExceptT . (parseDBFile <=< findDBFile) findDBFile :: String -> EIO (DBType, FilePath) findDBFile term = case term of (c:_) -> dbFileM c term `orLeft` "No terminfo db found" _ -> hoistEither $ Left "User specified null terminal name" where orLeft = flip noteT dbFileM c t = dirTreeDB c t <|> berkeleyDB -- | Not implemented berkeleyDB :: MaybeT IO (DBType, FilePath) berkeleyDB = nothing dirTreeDB :: Char -> String -> MaybeT IO (DBType, FilePath) dirTreeDB c term = MaybeT $ do path <- findFirst =<< map (</> [c] </> term) <$> terminfoDBLocs return $ (,) DirTreeDB <$> path findFirst :: [FilePath] -> IO (Maybe FilePath) findFirst = fmap headMay . filterM doesFileExist parseDBFile :: (DBType, FilePath) -> EIO TIDatabase parseDBFile (db, f) = case db of DirTreeDB -> extractDirTreeDB f BerkeleyDB -> hoistEither $ Left "BerkeleyDB support not yet implemented" -- | Extract a 'TIDatabase' from the specified file. IO exceptions are -- left to their own devices. extractDirTreeDB :: FilePath -> EIO TIDatabase extractDirTreeDB = hoistEither . parseDB <=< rightT . B.hGetContents <=< rightT . flip openBinaryFile ReadMode where rightT = except . fmap Right queryBoolTermCap :: TIDatabase -> BoolTermCap -> Bool queryBoolTermCap (TIDatabase (TCBMap vals) _ _) cap = fromMaybe False $ M.lookup cap vals queryNumTermCap :: TIDatabase -> NumTermCap -> Maybe Int queryNumTermCap (TIDatabase _ (TCNMap vals) _) cap = M.lookup cap vals -- | As this is a dead simple module, no \'smart\' handling of the -- returned string is implemented. In particular, placeholders for -- buffer characters and command arguments are left as-is. This will be -- rectified eventually, probably in a separate module. queryStrTermCap :: TIDatabase -> StrTermCap -> Maybe String queryStrTermCap (TIDatabase _ _ (TCSMap vals)) cap = M.lookup cap vals -- -- DOCUMENTATION -- -- $queryFuncs -- -- For each of these three actions, the first argument is the database to -- query, and the second argument is the capability to look up. -- -- I'm not super proud of this interface, but it's the best I can manage at -- present without requiring lots of mostly-empty case expressions. Perhaps -- someone will suggest a more interesting solution. -- $capabilities -- -- /see/ "System.Terminfo.Caps" -- -- There are no less than 497 capabilities specified in term.h on my -- Intel-based Ubuntu 12.04 notebook (slightly fewer in the terminfo(5) man -- page). The naive way of making these available to the user is as data -- constructors, and that is what I have done here. -- -- The number of constructors absolutely crushes the namespace. I have -- sequestered them into their own module to try to alleviate the pain.