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/help.cxx | 106 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 16 deletions(-) (limited to 'bpkg/help.cxx') 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