From ae9595c3212bf19958988bf33587ed65dfec57e8 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Fri, 25 Dec 2020 21:40:32 -0800 Subject: [PATCH] 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. --- hledger/Hledger/Cli/Commands/Help.hs | 29 +++---- hledger/Hledger/Cli/Commands/Help.md | 17 ++-- hledger/Hledger/Cli/DocFiles.hs | 121 +++++++++++++++++---------- hledger/Hledger/Cli/Main.hs | 8 +- 4 files changed, 102 insertions(+), 73 deletions(-) diff --git a/hledger/Hledger/Cli/Commands/Help.hs b/hledger/Hledger/Cli/Commands/Help.hs index 9b276ff38..ef32e8228 100644 --- a/hledger/Hledger/Cli/Commands/Help.hs +++ b/hledger/Hledger/Cli/Commands/Help.hs @@ -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 diff --git a/hledger/Hledger/Cli/Commands/Help.md b/hledger/Hledger/Cli/Commands/Help.md index 9160d18aa..c54ff8114 100644 --- a/hledger/Hledger/Cli/Commands/Help.md +++ b/hledger/Hledger/Cli/Commands/Help.md @@ -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. diff --git a/hledger/Hledger/Cli/DocFiles.hs b/hledger/Hledger/Cli/DocFiles.hs index 3a0a50b51..126b119ae 100644 --- a/hledger/Hledger/Cli/DocFiles.hs +++ b/hledger/Hledger/Cli/DocFiles.hs @@ -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 diff --git a/hledger/Hledger/Cli/Main.hs b/hledger/Hledger/Cli/Main.hs index c6e3a7d78..8658bafe1 100644 --- a/hledger/Hledger/Cli/Main.hs +++ b/hledger/Hledger/Cli/Main.hs @@ -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))