{-# LANGUAGE TemplateHaskell, OverloadedStrings, PackageImports #-}
{-|

Embedded documentation files in various formats, and helpers for viewing them.

|-}

module Hledger.Cli.DocFiles (

   Topic
  -- ,toolDocs
  -- ,toolDocNames
  -- ,toolDocMan
  -- ,toolDocTxt
  -- ,toolDocInfo
  ,printHelpForTopic
  ,runManForTopic
  ,runInfoForTopic
  ,runPagerForTopic

  ) where

import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BC
import Data.Maybe (fromMaybe, isNothing)
import Data.String
import System.IO
import System.IO.Temp
import System.Process

import Hledger.Utils (first3, second3, third3, embedFileRelative)
import Text.Printf (printf)
import System.Environment (lookupEnv)
import Hledger.Utils.Debug

-- The name of any hledger executable.
type Tool = String

-- Any heading in the hledger user manual (and perhaps later the hledger-ui/hledger-web manuals).
type Topic = String

-- | The main hledger manuals as source for man, info and as plain text.
-- Only files under the current package directory can be embedded,
-- so some of these are symlinked from the other package directories.
toolDocs :: [(Tool, (ByteString, ByteString, ByteString))]
toolDocs :: [(Tool, (ByteString, ByteString, ByteString))]
toolDocs = [
   (Tool
"hledger",
    ($(embedFileRelative "embeddedfiles/hledger.1")
    ,$(embedFileRelative "embeddedfiles/hledger.txt")
    ,$(embedFileRelative "embeddedfiles/hledger.info")
    ))
  ,(Tool
"hledger-ui",
    ($(embedFileRelative "embeddedfiles/hledger-ui.1")
    ,$(embedFileRelative "embeddedfiles/hledger-ui.txt")
    ,$(embedFileRelative "embeddedfiles/hledger-ui.info")
    ))
  ,(Tool
"hledger-web",
    ($(embedFileRelative "embeddedfiles/hledger-web.1")
    ,$(embedFileRelative "embeddedfiles/hledger-web.txt")
    ,$(embedFileRelative "embeddedfiles/hledger-web.info")
    ))
  ]

-- toolNames :: [Tool]
-- toolNames = map fst toolDocs

-- | Get the manual as plain text for this tool, or a not found message.
toolDocTxt :: Tool -> ByteString
toolDocTxt :: Tool -> ByteString
toolDocTxt Tool
name =
  ByteString
-> ((ByteString, ByteString, ByteString) -> ByteString)
-> Maybe (ByteString, ByteString, ByteString)
-> ByteString
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Tool -> ByteString
forall a. IsString a => Tool -> a
fromString (Tool -> ByteString) -> Tool -> ByteString
forall a b. (a -> b) -> a -> b
$ Tool
"No text manual found for tool: "Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
name) (ByteString, ByteString, ByteString) -> ByteString
forall a b c. (a, b, c) -> b
second3 (Maybe (ByteString, ByteString, ByteString) -> ByteString)
-> Maybe (ByteString, ByteString, ByteString) -> ByteString
forall a b. (a -> b) -> a -> b
$ Tool
-> [(Tool, (ByteString, ByteString, ByteString))]
-> Maybe (ByteString, ByteString, ByteString)
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup Tool
name [(Tool, (ByteString, ByteString, ByteString))]
toolDocs

-- | Get the manual as man source (nroff) for this tool, or a not found message.
toolDocMan :: Tool -> ByteString
toolDocMan :: Tool -> ByteString
toolDocMan Tool
name =
  ByteString
-> ((ByteString, ByteString, ByteString) -> ByteString)
-> Maybe (ByteString, ByteString, ByteString)
-> ByteString
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Tool -> ByteString
forall a. IsString a => Tool -> a
fromString (Tool -> ByteString) -> Tool -> ByteString
forall a b. (a -> b) -> a -> b
$ Tool
"No man page found for tool: "Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
name) (ByteString, ByteString, ByteString) -> ByteString
forall a b c. (a, b, c) -> a
first3 (Maybe (ByteString, ByteString, ByteString) -> ByteString)
-> Maybe (ByteString, ByteString, ByteString) -> ByteString
forall a b. (a -> b) -> a -> b
$ Tool
-> [(Tool, (ByteString, ByteString, ByteString))]
-> Maybe (ByteString, ByteString, ByteString)
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup Tool
name [(Tool, (ByteString, ByteString, ByteString))]
toolDocs

-- | Get the manual as info source (texinfo) for this tool, or a not found message.
toolDocInfo :: Tool -> ByteString
toolDocInfo :: Tool -> ByteString
toolDocInfo Tool
name =
  ByteString
-> ((ByteString, ByteString, ByteString) -> ByteString)
-> Maybe (ByteString, ByteString, ByteString)
-> ByteString
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Tool -> ByteString
forall a. IsString a => Tool -> a
fromString (Tool -> ByteString) -> Tool -> ByteString
forall a b. (a -> b) -> a -> b
$ Tool
"No info manual found for tool: "Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
name) (ByteString, ByteString, ByteString) -> ByteString
forall a b c. (a, b, c) -> c
third3 (Maybe (ByteString, ByteString, ByteString) -> ByteString)
-> Maybe (ByteString, ByteString, ByteString) -> ByteString
forall a b. (a -> b) -> a -> b
$ Tool
-> [(Tool, (ByteString, ByteString, ByteString))]
-> Maybe (ByteString, ByteString, ByteString)
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup Tool
name [(Tool, (ByteString, ByteString, ByteString))]
toolDocs

-- | Print plain text help for this tool.
-- Takes an optional topic argument for convenience but it is currently ignored.
printHelpForTopic :: Tool -> Maybe Topic -> IO ()
printHelpForTopic :: Tool -> Maybe Tool -> IO ()
printHelpForTopic Tool
tool Maybe Tool
_mtopic =
  ByteString -> IO ()
BC.putStr (Tool -> ByteString
toolDocTxt Tool
tool)

-- | Display plain text help for this tool, scrolled to the given topic
-- if provided, using the given pager executable.
-- Note when a topic is provided we ignore the provided pager and
-- use the "less" executable in $PATH.
runPagerForTopic :: Tool -> Maybe Topic -> IO ()
runPagerForTopic :: Tool -> Maybe Tool -> IO ()
runPagerForTopic Tool
tool Maybe Tool
mtopic = do
  -- avoids a temp file but different from the others and not sure how to make it scroll
  -- pager <- fromMaybe "less" <$> lookupEnv "PAGER"
  -- (Just inp, _, _, ph) <- createProcess (proc pager []){
  --   std_in=CreatePipe
  --   }
  -- BC.hPutStrLn inp (toolDocTxt tool)
  -- _ <- waitForProcess ph
  -- return ()
  
  Tool -> (Tool -> Handle -> IO ()) -> IO ()
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
Tool -> (Tool -> Handle -> m a) -> m a
withSystemTempFile (Tool
"hledger-"Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
toolTool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
".txt") ((Tool -> Handle -> IO ()) -> IO ())
-> (Tool -> Handle -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Tool
f Handle
h -> do
    Handle -> ByteString -> IO ()
BC.hPutStrLn Handle
h (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ Tool -> ByteString
toolDocTxt Tool
tool
    Handle -> IO ()
hClose Handle
h
    let defpager :: Tool
defpager = Tool
"less -is"
    Tool
envpager <- Tool -> Maybe Tool -> Tool
forall a. a -> Maybe a -> a
fromMaybe Tool
defpager (Maybe Tool -> Tool) -> IO (Maybe Tool) -> IO Tool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Tool -> IO (Maybe Tool)
lookupEnv Tool
"PAGER"
    -- force the use of less if a topic is provided, since we know how to scroll it
    let pager :: Tool
pager = if Maybe Tool -> Bool
forall a. Maybe a -> Bool
isNothing Maybe Tool
mtopic then Tool
envpager else Tool
defpager
    Tool -> IO ()
callCommand (Tool -> IO ()) -> Tool -> IO ()
forall a b. (a -> b) -> a -> b
$ Tool -> Tool -> Tool
forall a. Show a => Tool -> a -> a
dbg1 Tool
"pager command" (Tool -> Tool) -> Tool -> Tool
forall a b. (a -> b) -> a -> b
$ 
      Tool
pager Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++ Tool -> (Tool -> Tool) -> Maybe Tool -> Tool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Tool
"" (Tool -> Tool -> Tool
forall r. PrintfType r => Tool -> r
printf Tool
" +'/^(   )?%s'") Maybe Tool
mtopic Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++ Tool
" " Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++ Tool
f

-- | Display a man page for this tool, scrolled to the given topic if provided, 
-- using the "man" executable in $PATH. Note when a topic is provided we force 
-- man to use the "less" executable in $PATH, ignoring $MANPAGER and $PAGER.
runManForTopic :: Tool -> Maybe Topic -> IO ()
runManForTopic :: Tool -> Maybe Tool -> IO ()
runManForTopic Tool
tool Maybe Tool
mtopic =
  Tool -> (Tool -> Handle -> IO ()) -> IO ()
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
Tool -> (Tool -> Handle -> m a) -> m a
withSystemTempFile (Tool
"hledger-"Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
toolTool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
".nroff") ((Tool -> Handle -> IO ()) -> IO ())
-> (Tool -> Handle -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Tool
f Handle
h -> do
    Handle -> ByteString -> IO ()
BC.hPutStrLn Handle
h (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ Tool -> ByteString
toolDocMan Tool
tool
    Handle -> IO ()
hClose Handle
h
    -- the temp file path will presumably have a slash in it, so man should read it
    Tool -> IO ()
callCommand (Tool -> IO ()) -> Tool -> IO ()
forall a b. (a -> b) -> a -> b
$ Tool -> Tool -> Tool
forall a. Show a => Tool -> a -> a
dbg1 Tool
"man command" (Tool -> Tool) -> Tool -> Tool
forall a b. (a -> b) -> a -> b
$ 
      Tool
"man " Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++ Tool
f Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++ Tool -> (Tool -> Tool) -> Maybe Tool -> Tool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Tool
"" (Tool -> Tool -> Tool
forall r. PrintfType r => Tool -> r
printf Tool
" -P \"less -is +'/^(   )?%s'\"") Maybe Tool
mtopic

-- | Display an info manual for this topic, opened at the given topic if provided,
-- using the "info" executable in $PATH.
runInfoForTopic :: Tool -> Maybe Topic -> IO ()
runInfoForTopic :: Tool -> Maybe Tool -> IO ()
runInfoForTopic Tool
tool Maybe Tool
mtopic =
  Tool -> (Tool -> Handle -> IO ()) -> IO ()
forall (m :: * -> *) a.
(MonadIO m, MonadMask m) =>
Tool -> (Tool -> Handle -> m a) -> m a
withSystemTempFile (Tool
"hledger-"Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
toolTool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++Tool
".info") ((Tool -> Handle -> IO ()) -> IO ())
-> (Tool -> Handle -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Tool
f Handle
h -> do
    Handle -> ByteString -> IO ()
BC.hPutStrLn Handle
h (ByteString -> IO ()) -> ByteString -> IO ()
forall a b. (a -> b) -> a -> b
$ Tool -> ByteString
toolDocInfo Tool
tool
    Handle -> IO ()
hClose Handle
h
    Tool -> IO ()
callCommand (Tool -> IO ()) -> Tool -> IO ()
forall a b. (a -> b) -> a -> b
$ Tool -> Tool -> Tool
forall a. Show a => Tool -> a -> a
dbg1 Tool
"info command" (Tool -> Tool) -> Tool -> Tool
forall a b. (a -> b) -> a -> b
$
      Tool
"info -f " Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++ Tool
f Tool -> Tool -> Tool
forall a. [a] -> [a] -> [a]
++ Tool -> (Tool -> Tool) -> Maybe Tool -> Tool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Tool
"" (Tool -> Tool -> Tool
forall r. PrintfType r => Tool -> r
printf Tool
" -n '%s'") Maybe Tool
mtopic