cless-0.3.0.0: src/Main.hs
{-# LANGUAGE RecordWildCards #-}
module Main (main) where
import Control.Exception
import Control.Monad
import Data.Char
import Data.Maybe
import Data.Monoid
import Data.String
import Data.Version
import Options.Applicative
import System.Console.Terminfo
import System.Console.Terminfo.Color as Terminfo
import System.Console.Terminfo.PrettyPrint
import System.Environment
import System.IO
import System.Process
import Text.Highlighting.Kate as Kate
import Text.PrettyPrint.Free hiding ((<>))
import Text.Printf
import Paths_cless (version)
main :: IO ()
main = join $ execParser opts where
opts = info (helper <*> cmd)
( fullDesc
<> progDesc "Print the content of FILE with syntax highlighting"
<> header "cless: Colorized LESS" )
cmd = process
<$> switch ( long "version" <> short 'v'
<> help "Show version information" )
<*> switch ( long "list-langs" <> short 'L'
<> help "Show the list of supported languages" )
<*> switch ( long "list-styles" <> short 'S'
<> help "Show the list of supported styles" )
<*> switch ( long "LINE-NUMBERS" <> short 'N'
<> help "Show line numbers" )
<*> optional (strOption ( long "lang" <> short 'l'
<> metavar "LANG"
<> help "Specify language name" ) )
<*> optional (strOption ( long "style" <> short 's'
<> metavar "STYLE"
<> help "Specify style name (default 'pygments')" ) )
<*> optional (argument str (metavar "FILE"))
styles :: [(String, Style)]
styles =
[ ("pygments" , pygments )
, ("kate" , kate )
, ("espresso" , espresso )
, ("tango" , tango )
, ("haddock" , haddock )
, ("monochrome", monochrome)
, ("zenburn" , zenburn )
]
defaultPager :: String
defaultPager = "less -R"
defaultTerm :: String
defaultTerm = "xterm-256color"
defaultStyle :: Style
defaultStyle = pygments
process :: Bool -> Bool -> Bool -> Bool -> Maybe String -> Maybe String -> Maybe FilePath -> IO ()
process showVer showLangs showStyles linum mb_lang mb_stylename mb_file
| showVer =
putStrLn $ "cless version " ++ showVersion version
| showLangs =
mapM_ putStrLn languages
| showStyles =
mapM_ (putStrLn . fst) styles
| otherwise = do
process' linum mb_lang mb_stylename mb_file
process' :: Bool -> Maybe String -> Maybe String -> Maybe FilePath -> IO ()
process' linum mb_lang mb_stylename mb_file = do
con <- case mb_file of
Just file -> readFile file
Nothing -> do
isTerm <- hIsTerminalDevice stdin
when isTerm $
error "Missing filename (\"cless --help\" for help)"
getContents
let lang = determineLanguage mb_lang mb_file con
style = maybe defaultStyle findStyle mb_stylename
-- to raise error eagerly
evaluate lang
evaluate style
let ss = highlightAs lang con
doc = ppr linum style ss <> linebreak
sdoc = renderPretty 0.6 80 (prettyTerm doc)
termType <- fromMaybe defaultTerm <$> lookupEnv "TERM"
pager <- fromMaybe defaultPager <$> lookupEnv "PAGER"
term <- setupTerm $ if termType == "screen" then defaultTerm else termType
bracket
(createProcess (shell pager) { std_in = CreatePipe } )
( \(_, _, _, ph) -> waitForProcess ph )
$ \(Just h, _, _, _) -> do
case getCapability term $ evalTermState $ displayCap sdoc of
Just output -> hRunTermOutput h term output
Nothing -> displayIO h sdoc
hClose h
-- determin using language:
-- 1. user specified (must be correct)
-- 2. filename
-- 3. content
determineLanguage :: Maybe String -> Maybe String -> String -> String
determineLanguage mb_lang mb_file content = fromMaybe "plain" $
(isValid <$> mb_lang) <|>
(listToMaybe . languagesByFilename =<< mb_file) <|>
(findSupportedLanguage =<< detectLanguage content)
where
isValid lang
| Just lang' <- findSupportedLanguage lang = lang'
| otherwise = error $ "Unsupported language: " ++ lang
-- detect language from shebang
detectLanguage :: String -> Maybe String
detectLanguage ss
| take 2 ss == "#!" =
let sb = head $ lines ss
in listToMaybe $ catMaybes
[ findSupportedLanguage w
| w <- words $ map (\c -> if c == '/' then ' ' else c) sb
]
| otherwise =
Nothing
findSupportedLanguage :: String -> Maybe String
findSupportedLanguage lang
| map toLower lang `elem` map (map toLower) languages = Just lang
| (lang': _) <- languagesByExtension lang = Just lang'
| otherwise = Nothing
findStyle :: String -> Style
findStyle name =
fromMaybe (error $ "invalid style name: " ++ name)
$ lookup name styles
ppr :: Bool -> Style -> [SourceLine] -> TermDoc
ppr linum Style{..} = vcat . zipWith addLinum [1..] . map (hcat . map token) where
addLinum ln line
| linum =
let lns = text $ printf "%7d " (ln :: Int)
in withColors lineNumberColor lineNumberBackgroundColor lns
<> line
| otherwise = line
token (tokenType, ss) =
tokenEffect tokenType $ fromString ss
tokenEffect :: TokenType -> TermDoc -> TermDoc
tokenEffect tokenType =
let tokenStyle = fromMaybe defs $ lookup tokenType tokenStyles
defs = defStyle { tokenColor = defaultColor
, tokenBackground = Nothing -- backgroundColor
}
in styleToEffect tokenStyle
styleToEffect TokenStyle{..} =
withColors tokenColor tokenBackground .
with (if tokenBold then Bold else Nop) .
-- with (if tokenItalic then Standout else Nop) .
with (if tokenUnderline then Underline else Nop)
withColors foreground background =
with (maybe Nop (Foreground . cnvColor) foreground) .
with (maybe Nop (Background . cnvColor) background)
cnvColor :: Kate.Color -> Terminfo.Color
cnvColor (RGB r g b) = ColorNumber
$ 16
+ lucol (fromIntegral r) * 6 * 6
+ lucol (fromIntegral g) * 6
+ lucol (fromIntegral b)
where
tbl = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff :: Int]
lucol v = snd $ minimum [ (abs $ a - v, i) | (a, i) <- zip tbl [0..] ]