diff --git a/lib/mu-server.cc b/lib/mu-server.cc index 9c3db9f6..e6a4571f 100644 --- a/lib/mu-server.cc +++ b/lib/mu-server.cc @@ -83,14 +83,15 @@ struct Server::Private { // // output // - void output_sexp(Sexp&& sexp, bool flush = false) const + void output_sexp(Sexp&& sexp,Server::OutputFlags flags = {}) const { if (output_) - output_(std::move(sexp), flush); + output_(std::move(sexp), flags); } - void output_sexp(Sexp::List&& lst, bool flush = false) const + + void output_sexp(Sexp::List&& lst, Server::OutputFlags flags = {}) const { - output_sexp(Sexp::make_list(std::move(lst)), flush); + output_sexp(Sexp::make_list(std::move(lst)), flags); } size_t output_results(const QueryResults& qres, size_t batch_size) const; @@ -520,6 +521,7 @@ Server::Private::contacts_handler(const Parameters& params) const auto personal = get_bool_or(params, ":personal"); const auto afterstr = get_string_or(params, ":after"); const auto tstampstr = get_string_or(params, ":tstamp"); + const auto maxnum = get_int_or(params, ":maxnum", 0 /*unlimited*/); const auto after{afterstr.empty() ? 0 : parse_date_time(afterstr, true).value_or(0)}; @@ -550,18 +552,20 @@ Server::Private::contacts_handler(const Parameters& params) contact.add_prop(":address", Sexp::make_string(ci.display_name(true/*encode-if-needed*/))); contact.add_prop(":rank", Sexp::make_number(rank)); - - contacts.add(Sexp::make_list(std::move(contact))); - }); + auto contacts_sexp{Sexp::make_list(std::move(contact))}; + contacts_sexp.formatting_opts |= Sexp::FormattingOptions::SplitList; + contacts.add(std::move(contacts_sexp)); + }, static_cast(maxnum)); Sexp::List seq; seq.add_prop(":contacts", Sexp::make_list(std::move(contacts))); seq.add_prop(":tstamp", Sexp::make_string(format("%" G_GINT64_FORMAT, g_get_monotonic_time()))); + /* dump the contacts cache as a giant sexp */ g_debug("sending %d of %zu contact(s)", rank, store().contacts_cache().size()); - output_sexp(std::move(seq)); + output_sexp(std::move(seq), Server::OutputFlags::SplitList); } /* get a *list* of all messages with the given message id */ @@ -651,7 +655,9 @@ Server::Private::output_results(const QueryResults& qres, size_t batch_size) con // construct sexp for a single header. auto qm{mi.query_match()}; - headers.add(build_message_sexp(*msg, mi.doc_id(), qm)); + auto msgsexp{build_message_sexp(*msg, mi.doc_id(), qm)}; + msgsexp.formatting_opts |= Sexp::FormattingOptions::SplitList; + headers.add(std::move(msgsexp)); // we output up-to-batch-size lists of messages. It's much // faster (on the emacs side) to handle such batches than single // headers. @@ -804,9 +810,11 @@ Server::Private::index_handler(const Parameters& params) indexer().start(conf); while (indexer().is_running()) { std::this_thread::sleep_for(std::chrono::milliseconds(2000)); - output_sexp(get_stats(indexer().progress(), "running"), true); + output_sexp(get_stats(indexer().progress(), "running"), + Server::OutputFlags::Flush); } - output_sexp(get_stats(indexer().progress(), "complete"), true); + output_sexp(get_stats(indexer().progress(), "complete"), + Server::OutputFlags::Flush); }); } diff --git a/lib/mu-server.hh b/lib/mu-server.hh index 811ad6d4..95c7ffe2 100644 --- a/lib/mu-server.hh +++ b/lib/mu-server.hh @@ -24,17 +24,32 @@ #include #include +#include #include namespace Mu { /** - * @brief Implements the mu server, as used by mu4e. + * @brief Implements the mu server, as used by mu4e. * */ class Server { public: - using Output = std::function; + enum struct OutputFlags { + None = 0, + SplitList = 1 << 0, + /**< insert newlines between list items */ + Flush = 1 << 1, + /**< flush output buffer after */ + }; + + /** + * Prototype for output function + * + * @param sexp an s-expression + * @param flags flags that influence the behavior + */ + using Output = std::function; /** * Construct a new server @@ -63,6 +78,9 @@ private: struct Private; std::unique_ptr priv_; }; +MU_ENABLE_BITOPS(Server::OutputFlags); + } // namespace Mu + #endif /* MU_SERVER_HH__ */ diff --git a/lib/utils/mu-sexp.cc b/lib/utils/mu-sexp.cc index 69ab9c31..ef8182e4 100644 --- a/lib/utils/mu-sexp.cc +++ b/lib/utils/mu-sexp.cc @@ -188,6 +188,9 @@ Sexp::to_sexp_string() const first = false; } sstrm << ')'; + + if (any_of(formatting_opts & FormattingOptions::SplitList)) + sstrm << '\n'; break; } case Type::String: @@ -225,6 +228,8 @@ Sexp::to_json_string() const first = false; } sstrm << "}"; + if (any_of(formatting_opts & FormattingOptions::SplitList)) + sstrm << '\n'; } else { // other lists become arrays. sstrm << '['; bool first{true}; @@ -233,6 +238,8 @@ Sexp::to_json_string() const first = false; } sstrm << ']'; + if (any_of(formatting_opts & FormattingOptions::SplitList)) + sstrm << '\n'; } break; } diff --git a/lib/utils/mu-sexp.hh b/lib/utils/mu-sexp.hh index 4a1e8b67..725a37b1 100644 --- a/lib/utils/mu-sexp.hh +++ b/lib/utils/mu-sexp.hh @@ -342,6 +342,14 @@ struct Sexp { return is_prop_list(list().begin() + 1, list().end()); } + enum struct FormattingOptions { + Default = 0, /**< Nothing in particular */ + SplitList = 1 << 0, /**< Insert newline after list item */ + }; + + FormattingOptions formatting_opts{}; /**< Formatting option for the + * string output */ + private: Sexp(Type typearg, std::string&& valuearg) : type_{typearg}, value_{std::move(valuearg)} { if (is_list()) @@ -395,11 +403,21 @@ static inline std::ostream& operator<<(std::ostream& os, Sexp::Type id) { switch (id) { - case Sexp::Type::List: os << "list"; break; - case Sexp::Type::String: os << "string"; break; - case Sexp::Type::Number: os << "number"; break; - case Sexp::Type::Symbol: os << "symbol"; break; - case Sexp::Type::Empty: os << "empty"; break; + case Sexp::Type::List: + os << "list"; + break; + case Sexp::Type::String: + os << "string"; + break; + case Sexp::Type::Number: + os << "number"; + break; + case Sexp::Type::Symbol: + os << "symbol"; + break; + case Sexp::Type::Empty: + os << "empty"; + break; default: throw std::runtime_error("unknown node type"); } @@ -419,6 +437,7 @@ operator<<(std::ostream& os, const Sexp::List& sexp) os << Sexp::make_list(Sexp::List(sexp)); return os; } +MU_ENABLE_BITOPS(Sexp::FormattingOptions); } // namespace Mu diff --git a/mu/mu-cmd-server.cc b/mu/mu-cmd-server.cc index 85fd06a7..54daea14 100644 --- a/mu/mu-cmd-server.cc +++ b/mu/mu-cmd-server.cc @@ -59,7 +59,7 @@ install_sig_handler(void) for (i = 0; i != G_N_ELEMENTS(sigs); ++i) if (sigaction(sigs[i], &action, NULL) != 0) g_critical("set sigaction for %d failed: %s", - sigs[i], g_strerror(errno)); + sigs[i], g_strerror(errno)); } /* @@ -82,16 +82,22 @@ cookie(size_t n) } static void -output_sexp_stdout(Sexp&& sexp, bool flush = false) +output_sexp_stdout(Sexp&& sexp, Server::OutputFlags flags) { + /* if requested, insert \n between list elements; note: + * is _not_ inherited by children */ + if (any_of(flags & Server::OutputFlags::SplitList)) + sexp.formatting_opts |= Sexp::FormattingOptions::SplitList; + const auto str{sexp.to_sexp_string()}; + cookie(str.size() + 1); if (G_UNLIKELY(::puts(str.c_str()) < 0)) { g_critical("failed to write output '%s'", str.c_str()); ::raise(SIGTERM); /* terminate ourselves */ } - if (flush) + if (any_of(flags & Server::OutputFlags::Flush)) std::fflush(stdout); } @@ -103,7 +109,8 @@ report_error(const Mu::Error& err) noexcept e.add_prop(":error", Sexp::make_number(static_cast(err.code()))); e.add_prop(":message", Sexp::make_string(err.what())); - output_sexp_stdout(Sexp::make_list(std::move(e)), true /*flush*/); + output_sexp_stdout(Sexp::make_list(std::move(e)), + Server::OutputFlags::Flush); } MuError @@ -113,15 +120,15 @@ try { Server server{store, output_sexp_stdout}; g_message("created server with store @ %s; maildir @ %s; debug-mode %s", - store.properties().database_path.c_str(), - store.properties().root_maildir.c_str(), - opts->debug ? "yes" : "no"); + store.properties().database_path.c_str(), + store.properties().root_maildir.c_str(), + opts->debug ? "yes" : "no"); tty = ::isatty(::fileno(stdout)); const auto eval = std::string{opts->commands ? "(help :full t)" - : opts->eval ? opts->eval - : ""}; + : opts->eval ? opts->eval + : ""}; if (!eval.empty()) { server.invoke(eval); return MU_OK;