cli/help: --info/--man/help show CMD/topic-specific docs

hledger CMD --info will now open CMD's info node,
hledger CMD --man will scroll the man page to CMD's section, and
hledger help -i/-m/-p TOPIC will do similar.
This is not perfectly robust but hopefully will be good enough in
practice.

Also the help command's long --info/--man/--pager flags have been
dropped in favour of -i/-m/-p.
This commit is contained in:
Simon Michael 2020-12-25 21:40:32 -08:00
parent 54af71a0b1
commit ae9595c321
4 changed files with 102 additions and 73 deletions

View File

@ -29,18 +29,19 @@ import Hledger.Data.RawOptions
import Hledger.Data.Types
import Hledger.Cli.CliOptions
import Hledger.Cli.DocFiles
import Safe (headMay)
--import Hledger.Utils.Debug
helpmode = hledgerCommandMode
$(embedFileRelative "Hledger/Cli/Commands/Help.txt")
[flagNone ["info","i"] (setboolopt "info") "show the manual with info"
,flagNone ["man","m"] (setboolopt "man") "show the manual with man"
,flagNone ["pager","p"] (setboolopt "pager") "show the manual with $PAGER or less"
[flagNone ["i"] (setboolopt "info") "show the manual with info"
,flagNone ["m"] (setboolopt "man") "show the manual with man"
,flagNone ["p"] (setboolopt "pager") "show the manual with $PAGER or less"
,flagNone ["help","h"] (setboolopt "help") "show this help"
]
[]
[]
([], Nothing) -- Just $ argsFlag "[TOPIC]"
([], Just $ argsFlag "[TOPIC]")
-- | Display the hledger manual in various formats.
-- You can select a docs viewer with one of the `--info`, `--man`, `--pager` flags.
@ -52,12 +53,10 @@ help' opts _ = do
pagerprog <- fromMaybe "less" <$> lookupEnv "PAGER"
interactive <- hIsTerminalDevice stdout
let
-- args = take 1 $ listofstringopt "args" $ rawopts_ opts
-- topic = case args of
-- [pat] -> headMay [t | t <- docTopics, map toLower pat `isInfixOf` t]
-- _ -> Nothing
args = take 1 $ listofstringopt "args" $ rawopts_ opts
mtopic = headMay args
[info, man, pager, cat] =
[runInfoForTopic, runManForTopic, runPagerForTopic pagerprog, printHelpForTopic]
[runInfoForTopic, runManForTopic, runPagerForTopic, printHelpForTopic]
viewer
| boolopt "info" $ rawopts_ opts = info
| boolopt "man" $ rawopts_ opts = man
@ -66,13 +65,7 @@ help' opts _ = do
| "info" `elem` exes = info
| "man" `elem` exes = man
| pagerprog `elem` exes = pager
| "less" `elem` exes = pager
| otherwise = cat
viewer "hledger"
-- case topic of
-- Nothing -> putStrLn $ unlines [
-- "Please choose a manual by typing \"hledger help MANUAL\" (any substring is ok)."
-- ,"A viewer (info, man, $PAGER, or stdout) will be auto-selected,"
-- ,"or type \"hledger help -h\" to see options. Manuals available:"
-- ]
-- ++ "\n " ++ unwords docTopics
-- Just t -> viewer t
viewer "hledger" mtopic

View File

@ -1,16 +1,17 @@
help\
Show the hledger user manual in one of several formats.
Show the hledger user manual in one of several formats,
optionally positioned at a given TOPIC (if possible).
TOPIC is any heading, or heading prefix, in the manual.
Some examples: commands, print, 'auto postings', periodic.
_FLAGS
This command shows the user manual built in to this hledger version,
using the best viewer it can find.
This command shows the user manual built in to this hledger version.
It can be useful if the correct version of the hledger manual,
or the usual viewing tools, are not installed on your system.
It will use the first of these viewers that it finds in $PATH:
`info`, `man`, $PAGER, `less`, or stdout.
By default it uses the best viewer it can find in $PATH, in this order:
`info`, `man`, $PAGER (unless a topic is specified), `less`, or stdout.
When run non-interactively, it always uses stdout.
Or you can force a particular viewer with the
`--info/-i`, `--man/-m`, or `--pager/-p` flags.
Or you can select a particular viewer with the
`-i` (info), `-m` (man), or `-p` (pager) flags.

View File

@ -8,11 +8,11 @@ Embedded documentation files in various formats, and helpers for viewing them.
module Hledger.Cli.DocFiles (
Topic
,docFiles
,docTopics
,lookupDocNroff
,lookupDocTxt
,lookupDocInfo
-- ,toolDocs
-- ,toolDocNames
-- ,toolDocMan
-- ,toolDocTxt
-- ,toolDocInfo
,printHelpForTopic
,runManForTopic
,runInfoForTopic
@ -30,14 +30,22 @@ import System.IO.Temp
import System.Process
import Hledger.Utils (first3, second3, third3, embedFileRelative)
import Text.Printf (printf)
import Data.Maybe (fromMaybe)
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
-- | These are all the main hledger manuals, in man, txt, and info formats.
-- | The main hledger manuals as source for man, info and as plain text.
-- Only files under the current package directory can be embedded,
-- so most of these are symlinked here from the other package directories.
docFiles :: [(Topic, (ByteString, ByteString, ByteString))]
docFiles = [
-- so some of these are symlinked from the other package directories.
toolDocs :: [(Tool, (ByteString, ByteString, ByteString))]
toolDocs = [
("hledger",
($(embedFileRelative "embeddedfiles/hledger.1")
,$(embedFileRelative "embeddedfiles/hledger.txt")
@ -55,46 +63,73 @@ docFiles = [
))
]
docTopics :: [Topic]
docTopics = map fst docFiles
-- toolNames :: [Tool]
-- toolNames = map fst toolDocs
lookupDocTxt :: Topic -> ByteString
lookupDocTxt name =
maybe (fromString $ "No text manual found for topic: "++name) second3 $ lookup name docFiles
-- | Get the manual as plain text for this tool, or a not found message.
toolDocTxt :: Tool -> ByteString
toolDocTxt name =
maybe (fromString $ "No text manual found for tool: "++name) second3 $ lookup name toolDocs
lookupDocNroff :: Topic -> ByteString
lookupDocNroff name =
maybe (fromString $ "No man page found for topic: "++name) first3 $ lookup name docFiles
-- | Get the manual as man source (nroff) for this tool, or a not found message.
toolDocMan :: Tool -> ByteString
toolDocMan name =
maybe (fromString $ "No man page found for tool: "++name) first3 $ lookup name toolDocs
lookupDocInfo :: Topic -> ByteString
lookupDocInfo name =
maybe (fromString $ "No info manual found for topic: "++name) third3 $ lookup name docFiles
-- | Get the manual as info source (texinfo) for this tool, or a not found message.
toolDocInfo :: Tool -> ByteString
toolDocInfo name =
maybe (fromString $ "No info manual found for tool: "++name) third3 $ lookup name toolDocs
printHelpForTopic :: Topic -> IO ()
printHelpForTopic t =
BC.putStr (lookupDocTxt t)
-- | 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 _mtopic =
BC.putStr (toolDocTxt tool)
runPagerForTopic :: FilePath -> Topic -> IO ()
runPagerForTopic exe t = do
(Just inp, _, _, ph) <- createProcess (proc exe []){
std_in=CreatePipe
}
BC.hPutStrLn inp (lookupDocTxt t)
_ <- waitForProcess ph
return ()
runManForTopic :: Topic -> IO ()
runManForTopic t =
withSystemTempFile ("hledger-"++t++".nroff") $ \f h -> do
BC.hPutStrLn h $ lookupDocNroff t
-- | 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 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 ()
withSystemTempFile ("hledger-"++tool++".txt") $ \f h -> do
BC.hPutStrLn h $ toolDocTxt tool
hClose h
-- the temp file path will presumably have a slash in it, so man should read it
callCommand $ "man " ++ f
let defpager = "less -is"
envpager <- fromMaybe defpager <$> lookupEnv "PAGER"
-- force the use of less if a topic is provided, since we know how to scroll it
let pager = if mtopic==Nothing then envpager else defpager
callCommand $ dbg1 "pager command" $
pager ++ maybe "" (printf " +'/^( )?%s'") mtopic ++ " " ++ f
runInfoForTopic :: Topic -> IO ()
runInfoForTopic t =
withSystemTempFile ("hledger-"++t++".info") $ \f h -> do
BC.hPutStrLn h $ lookupDocInfo t
-- | 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 mtopic =
withSystemTempFile ("hledger-"++tool++".nroff") $ \f h -> do
BC.hPutStrLn h $ toolDocMan tool
hClose h
callCommand $ "info " ++ f
-- the temp file path will presumably have a slash in it, so man should read it
callCommand $ dbg1 "man command" $
"man " ++ f ++ maybe "" (printf " -P \"less -is +'/^( )?%s'\"") 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 mtopic =
withSystemTempFile ("hledger-"++tool++".info") $ \f h -> do
BC.hPutStrLn h $ toolDocInfo tool
hClose h
callCommand $ dbg1 "info command" $
"info " ++ f ++ maybe "" (printf " -n '%s'") mtopic

View File

@ -146,8 +146,8 @@ main = do
hasInfoFlag args = any (`elem` args) ["--info"]
f `orShowHelp` mode
| hasHelpFlag args = putStr $ showModeUsage mode
| hasInfoFlag args = runInfoForTopic "hledger"
| hasManFlag args = runManForTopic "hledger"
| hasInfoFlag args = runInfoForTopic "hledger" (headMay $ modeNames mode)
| hasManFlag args = runManForTopic "hledger" (headMay $ modeNames mode)
| otherwise = f
-- where
-- lastdocflag
@ -165,8 +165,8 @@ main = do
runHledgerCommand
-- high priority flags and situations. -h, then --help, then --info are highest priority.
| hasHelpFlag argsbeforecmd = dbgIO "" "-h/--help before command, showing general usage" >> printUsage
| hasInfoFlag argsbeforecmd = dbgIO "" "--info before command, showing general info manual" >> runInfoForTopic "hledger"
| hasManFlag argsbeforecmd = dbgIO "" "--man before command, showing general man page" >> runManForTopic "hledger"
| hasInfoFlag argsbeforecmd = dbgIO "" "--info before command, showing general info manual" >> runInfoForTopic "hledger" Nothing
| hasManFlag argsbeforecmd = dbgIO "" "--man before command, showing general man page" >> runManForTopic "hledger" Nothing
| not (hasHelpFlag argsaftercmd || hasInfoFlag argsaftercmd || hasManFlag argsaftercmd) && (hasVersion argsbeforecmd || (hasVersion argsaftercmd && isInternalCommand))
= putStrLn prognameandversion
| not (hasHelpFlag argsaftercmd || hasInfoFlag argsaftercmd || hasManFlag argsaftercmd) && (hasDetailedVersion argsbeforecmd || (hasDetailedVersion argsaftercmd && isInternalCommand))