terminfo-hs-0.2.1: src/System/Terminfo.hs
{-# 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.