tldr-0.7.0: app/Main.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE BangPatterns #-}
module Main
( main
) where
import Control.Monad
import Data.List (intercalate)
import Data.Semigroup ((<>))
import qualified Data.Set as Set
import Data.Version (showVersion)
import System.IO (stdout)
import Options.Applicative
import Paths_tldr (version)
import System.Directory
import System.Environment (getArgs, getExecutablePath, lookupEnv)
import System.FilePath
import System.Process.Typed
import Data.Char (toLower)
import Tldr
data TldrOpts = TldrOpts
{ tldrAction :: TldrCommand
} deriving (Show)
data TldrCommand
= UpdateIndex
| ViewPage ViewOptions
[String]
| About
deriving (Show, Eq, Ord)
data ViewOptions =
ViewOptions
{ platformOption :: Maybe String
, languageOption :: Maybe String
}
deriving (Show, Eq, Ord)
englishViewOptions :: ViewOptions -> ViewOptions
englishViewOptions xs = xs { languageOption = Just "en_US.utf8" }
programOptions :: Parser TldrOpts
programOptions =
(TldrOpts <$> (updateIndexCommand <|> viewPageCommand <|> aboutFlag))
updateIndexCommand :: Parser TldrCommand
updateIndexCommand =
flag'
UpdateIndex
(long "update" <> short 'u' <> help "Update offline cache of tldr pages")
aboutFlag :: Parser TldrCommand
aboutFlag = flag' About (long "about" <> short 'a' <> help "About this program")
viewOptionsParser :: Parser ViewOptions
viewOptionsParser = ViewOptions <$> platformFlag <*> languageFlag
viewPageCommand :: Parser TldrCommand
viewPageCommand =
ViewPage <$> viewOptionsParser <*>
some (strArgument (metavar "COMMAND" <> help "name of the command"))
platformFlag :: Parser (Maybe String)
platformFlag =
optional
(strOption
(long "platform" <> short 'p' <> metavar "PLATFORM" <>
help
("Prioritize specfic platform while searching. Valid values include " <>
platformHelpValue)))
where
platformHelpValue :: String
platformHelpValue = intercalate ", " platformDirs
languageFlag :: Parser (Maybe String)
languageFlag =
optional
(strOption
(long "language" <> short 'L' <> metavar "LOCALE" <>
help
("Preferred language for the page returned")))
tldrDirName :: String
tldrDirName = "tldr"
repoHttpsUrl :: String
repoHttpsUrl = "https://github.com/tldr-pages/tldr.git"
checkDirs :: [String]
checkDirs = "common" : platformDirs
platformDirs :: [String]
platformDirs = ["linux", "osx", "windows", "sunos"]
tldrInitialized :: IO Bool
tldrInitialized = do
dataDir <- getXdgDirectory XdgData tldrDirName
let dir2 = dataDir </> "tldr"
pages = dataDir </> "tldr" </> "pages"
exists <- mapM doesDirectoryExist [dataDir, dir2, pages]
return $ all (== True) exists
initializeTldrPages :: IO ()
initializeTldrPages = do
initialized <- tldrInitialized
unless initialized $ do
dataDir <- getXdgDirectory XdgData tldrDirName
createDirectoryIfMissing False dataDir
runProcess_ $ setWorkingDir dataDir $ proc "git" ["clone", repoHttpsUrl]
updateTldrPages :: IO ()
updateTldrPages = do
dataDir <- getXdgDirectory XdgData tldrDirName
let repoDir = dataDir </> "tldr"
repoExists <- doesDirectoryExist repoDir
case repoExists of
True ->
runProcess_ $
setWorkingDir (repoDir) $ proc "git" ["pull", "origin", "master"]
False -> initializeTldrPages
tldrParserInfo :: ParserInfo TldrOpts
tldrParserInfo =
info
(helper <*> versionOption <*> programOptions)
(fullDesc <> progDesc "tldr Client program" <>
header "tldr - Simplified and community-driven man pages")
where
versionOption :: Parser (a -> a)
versionOption =
infoOption
(showVersion version)
(long "version" <> short 'v' <> help "Show version")
pageExists :: FilePath -> IO (Maybe FilePath)
pageExists fname = do
exists <- doesFileExist fname
if exists
then return $ Just fname
else return Nothing
getPagePath :: Locale -> String -> [String] -> IO (Maybe FilePath)
getPagePath locale page platformDirs = do
dataDir <- getXdgDirectory XdgData tldrDirName
let currentLocale = case locale of
English -> "pages"
Other xs -> "pages." <> xs
Unknown xs -> "pages." <> xs
Missing -> "pages"
pageDir = dataDir </> "tldr" </> currentLocale
paths = map (\x -> pageDir </> x </> page <.> "md") platformDirs
foldr1 (<|>) <$> mapM pageExists paths
getCheckDirs :: ViewOptions -> [String]
getCheckDirs voptions =
case platformOption voptions of
Nothing -> checkDirs
Just platform -> nubOrd $ ["common", platform] <> checkDirs
-- | Strip out duplicates
nubOrd :: Ord a => [a] -> [a]
nubOrd = loop mempty
where
loop _ [] = []
loop !s (a:as)
| a `Set.member` s = loop s as
| otherwise = a : loop (Set.insert a s) as
handleAboutFlag :: IO ()
handleAboutFlag = do
path <- getExecutablePath
let content =
unlines
[ path <> " v" <> (showVersion version)
, "Copyright (C) 2017 Sibi Prabakaran"
, "Source available at https://github.com/psibi/tldr-hs"
]
putStr content
handleTldrOpts :: TldrOpts -> IO ()
handleTldrOpts opts@TldrOpts {..} = do
case tldrAction of
UpdateIndex -> updateTldrPages
About -> handleAboutFlag
vopts@(ViewPage voptions pages) -> do
let npage = intercalate "-" pages
locale <-
case (languageOption voptions) of
Nothing -> retriveLocale
Just lg -> pure $ computeLocale (Just lg)
fname <- getPagePath locale npage (getCheckDirs voptions)
case fname of
Just path -> renderPage path stdout
Nothing -> do
if checkLocale locale
then putStrLn ("No tldr entry for " <> (intercalate " " pages))
else handleTldrOpts
(opts
{ tldrAction =
ViewPage (englishViewOptions voptions) pages
})
checkLocale :: Locale -> Bool
checkLocale English = True
checkLocale _ = False
data Locale = English | Missing | Other String | Unknown String
retriveLocale :: IO Locale
retriveLocale = do
lang <- lookupEnv "LANG"
pure $ computeLocale lang
computeLocale :: Maybe String -> Locale
computeLocale lang = case map toLower <$> lang of
Nothing -> Missing
Just ('e':'n':_) -> English
Just (a:b:'_':_) -> Other (a:b:[])
Just (a:b:c:'_':_) -> Other (a:b:c:[])
Just str -> Unknown str
main :: IO ()
main = do
args <- getArgs
case execParserPure (prefs showHelpOnEmpty) tldrParserInfo args of
failOpts@(Failure _) -> handleParseResult failOpts >> return ()
Success opts -> handleTldrOpts opts
compOpts@(CompletionInvoked _) -> handleParseResult compOpts >> return ()