From 9ecfa10db4a4856d445910d440aa38ee5b2b3d26 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 25 Nov 2015 12:51:49 +0200 Subject: Implement help pager --- bpkg/bpkg.cxx | 19 +++-------- bpkg/help.cxx | 106 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 95 insertions(+), 30 deletions(-) diff --git a/bpkg/bpkg.cxx b/bpkg/bpkg.cxx index 386d284..e1cdce2 100644 --- a/bpkg/bpkg.cxx +++ b/bpkg/bpkg.cxx @@ -85,10 +85,7 @@ try } if (o.help ()) - { - help (help_options (), "", nullptr); - return 0; - } + return help (help_options (), "", nullptr); const common_options& co (o); @@ -131,16 +128,10 @@ try // If not, then it got to be a help topic. // if (cmd_argc != 1) - { - help (ho, cmd_argv[1], nullptr); - return 0; - } + return help (ho, cmd_argv[1], nullptr); } else - { - help (ho, "", nullptr); - return 0; - } + return help (ho, "", nullptr); } // Handle commands. @@ -162,7 +153,7 @@ try // if (cmd.pkg_verify ()) // { // if (h) - // help (ho, "pkg-verify", pkg_verify_options::print_usage); + // r = help (ho, "pkg-verify", pkg_verify_options::print_usage); // else // r = pkg_verify (parse (co, args), args); // @@ -173,7 +164,7 @@ try if (cmd.NP##CMD ()) \ { \ if (h) \ - help (ho, SP#CMD, print_bpkg_##NP##CMD##_usage); \ + r = help (ho, SP#CMD, print_bpkg_##NP##CMD##_usage); \ else \ r = NP##CMD (parse (co, args), args); \ \ diff --git a/bpkg/help.cxx b/bpkg/help.cxx index df4a286..31a7cdd 100644 --- a/bpkg/help.cxx +++ b/bpkg/help.cxx @@ -4,8 +4,19 @@ #include +#ifndef _WIN32 +# include // close() +#else +# include // _close() +#endif + +#include +#include // this_thread::sleep_for() #include +#include +#include + #include #include #include @@ -13,32 +24,95 @@ #include using namespace std; +using namespace butl; namespace bpkg { - static void - help () + struct pager { - print_bpkg_usage (cout); - } + pager (const string& name) + { + // First try less. + // + try + { + string prompt ("-Ps" + name + " (press q to quit, h for help)"); + + const char* args[] = { + "less", + "-R", // ANSI color + prompt.c_str (), + nullptr + }; + + p_ = process (args, -1); // Redirect child's stdin to a pipe. + + // Wait a bit and see if the pager has exited before reading + // anything (e.g., because exec() couldn't find the program). + // If you know a cleaner way to handle this, let me know (no, + // a select()-based approach doesn't work; the pipe is buffered + // and therefore is always ready for writing). + // + this_thread::sleep_for (chrono::milliseconds (50)); + + bool r; + if (p_.try_wait (r)) + { +#ifndef _WIN32 + ::close (p_.out_fd); +#else + _close (p_.out_fd); +#endif + } + else + os_.open (p_.out_fd); + } + catch (const process_error& e) + { + if (e.child ()) + exit (1); + } + } + + std::ostream& + stream () + { + return os_.is_open () ? os_ : std::cout; + } + + bool + wait () + { + os_.close (); + return p_.wait (); + } + + ~pager () {wait ();} + + private: + process p_; + ofdstream os_; + }; int help (const help_options&, const string& t, usage_function* usage) { - ostream& o (cout); - - if (usage != nullptr) // Command. - usage (o, cli::usage_para::none); - else if (t.empty ()) // General help. - help (); - else if (t == "common-options") // Help topics. + if (usage == nullptr) // Not a command. { - print_bpkg_common_options_long_usage (cout); + if (t.empty ()) // General help. + usage = &print_bpkg_usage; + else if (t == "common-options") // Help topics. + usage = &print_bpkg_common_options_long_usage; + else + fail << "unknown bpkg command/help topic '" << t << "'" << + info << "run 'bpkg help' for more information"; } - else - fail << "unknown bpkg command/help topic '" << t << "'" << - info << "run 'bpkg help' for more information"; - return 0; + pager p ("bpkg " + (t.empty () ? "help" : t)); + usage (p.stream (), cli::usage_para::none); + + // If the pager failed, assume it has issued some diagnostics. + // + return p.wait () ? 0 : 1; } } -- cgit v1.1