aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-11-25 12:51:49 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-11-25 12:51:49 +0200
commit9ecfa10db4a4856d445910d440aa38ee5b2b3d26 (patch)
tree2cbcfbc06adce166bf0ce2376d4c7300b9fdcb4e
parent87e476cf192b70c133a1bf00efa8586348326092 (diff)
Implement help pager
-rw-r--r--bpkg/bpkg.cxx19
-rw-r--r--bpkg/help.cxx106
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<pkg_verify_options> (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<NP##CMD##_options> (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 <bpkg/help>
+#ifndef _WIN32
+# include <unistd.h> // close()
+#else
+# include <io.h> // _close()
+#endif
+
+#include <chrono>
+#include <thread> // this_thread::sleep_for()
#include <iostream>
+#include <butl/process>
+#include <butl/fdstream>
+
#include <bpkg/types>
#include <bpkg/utility>
#include <bpkg/diagnostics>
@@ -13,32 +24,95 @@
#include <bpkg/bpkg-options>
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;
}
}