{-# LANGUAGE ApplicativeDo #-}
module Main where
import Docs.CLI.Directory
( AppCache(..)
, mkAppCacheDir
)
import Docs.CLI.Evaluate
( interactive
, evaluate
, evaluateCmd
, ShellState(..)
, Context(..)
, Cmd(..)
, Selection(..)
, HackageUrl(..)
, HoogleUrl(..)
, runCLI
, defaultHackageUrl
, defaultHoogleUrl
, moreInfoText
)
import Control.Concurrent.Async (withAsync)
import Control.Applicative (many, (<|>), optional)
import Control.Monad (void)
import Data.Maybe (fromMaybe)
import qualified Network.HTTP.Client.TLS as Http (tlsManagerSettings)
import qualified Network.HTTP.Client as Http
import qualified Options.Applicative as O
import qualified Options.Applicative.Help.Pretty as OP
import System.Environment (getEnv)
import System.FilePath.Posix ((</>))
import System.Directory (createDirectoryIfMissing, getHomeDirectory, getXdgDirectory, XdgDirectory(..))
import System.IO (hIsTerminalDevice, stdout)
import Data.Cache as Cache
data CacheOption = Unlimited | Off
data Options = Options
{ optQuery :: String
, optAppCacheDir :: Maybe FilePath
, optCache :: Maybe CacheOption
, optHoogle :: Maybe HoogleUrl
, optHackage :: Maybe HackageUrl
}
cachePolicy :: Maybe CacheOption -> AppCache -> IO Cache.EvictionPolicy
cachePolicy mCacheOpt (AppCache dir) =
case mCacheOpt of
Just Off -> return Cache.NoStorage
Just Unlimited -> eviction Cache.NoMaxBytes Cache.NoMaxAge
Nothing -> eviction (Cache.MaxBytes $ 100 * mb) (Cache.MaxAgeDays 20)
where
mb = 1024 * 1024
eviction bytes age = do
createDirectoryIfMissing True dir
return $ Cache.Evict bytes age (Store dir)
cliOptions :: O.ParserInfo Options
cliOptions = O.info (O.helper <*> parser) $ mconcat
[ O.fullDesc
, O.headerDoc $ Just $ OP.vcat
[ "haskell-docs-cli"
, ""
, OP.indent 2 $ OP.vcat
[ "Search Hoogle and view Hackage documentation from the command line."
, "Search modules, packages, types and functions by name or by approximate type signature."
]
]
, O.footerDoc $ Just $ moreInfoText <> OP.linebreak
]
where
parser = do
optQuery <- fmap unwords . many $ O.strArgument $ O.metavar "CMD"
optAppCacheDir <- optional $ O.strOption $ mconcat
[ O.long "cache-dir"
, O.metavar "PATH"
, O.help "Specify the directory for application cache (default: XDG_CACHE_HOME/haskell-docs-cli)."
]
optCache <- optional $ O.option readCache $ mconcat
[ O.long "cache"
, O.metavar "unlimited|off"
, O.help "Set a custom cache eviction policy"
]
optHoogle <- optional $ fmap HoogleUrl $ O.strOption $ mconcat
[ O.long "hoogle"
, O.metavar "URL"
, O.help "Address of Hoogle instance to be used"
]
optHackage <- optional $ fmap HackageUrl $ O.strOption $ mconcat
[ O.long "hackage"
, O.metavar "URL"
, O.help "Address of Hackage instance to be used"
]
pure $ Options {..}
where
readCache = O.maybeReader $ \str ->
case str of
"unlimited" -> Just Unlimited
"off" -> Just Off
_ -> Nothing
main :: IO ()
main = void $ do
Options{..} <- O.execParser cliOptions
manager <- Http.newManager Http.tlsManagerSettings
appCache <- mkAppCacheDir optAppCacheDir
policy <- cachePolicy optCache appCache
cache <- Cache.create policy
isTTY <- hIsTerminalDevice stdout
let state = ShellState
{ sContext = ContextEmpty
, sManager = manager
, sCache = cache
, sNoColours = not isTTY
, sHoogle = fromMaybe defaultHoogleUrl optHoogle
, sHackage = fromMaybe defaultHackageUrl optHackage
}
withAsync (Cache.enforce policy) $ \_ ->
runCLI state $
case optQuery of
"" -> interactive
input -> evaluate input
main' :: IO ()
main' = void $ do
Options{} <- O.execParser cliOptions
manager <- Http.newManager Http.tlsManagerSettings
appCache <- mkAppCacheDir Nothing
policy <- cachePolicy Nothing appCache
cache <- Cache.create policy
let state = ShellState
{ sContext = ContextEmpty
, sManager = manager
, sCache = cache
, sNoColours = False
, sHoogle = defaultHoogleUrl
, sHackage = defaultHackageUrl
}
runCLI state $ do
evaluateCmd (ViewDeclaration $ Search "completeWord +haskeline")