mirror of https://github.com/djcb/mu.git
sexp: allow for some prettified string output
Allow for adding newlines between list items
This commit is contained in:
parent
2a5c1e239c
commit
da8489d0f6
|
@ -83,14 +83,15 @@ struct Server::Private {
|
||||||
//
|
//
|
||||||
// output
|
// output
|
||||||
//
|
//
|
||||||
void output_sexp(Sexp&& sexp, bool flush = false) const
|
void output_sexp(Sexp&& sexp,Server::OutputFlags flags = {}) const
|
||||||
{
|
{
|
||||||
if (output_)
|
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;
|
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 personal = get_bool_or(params, ":personal");
|
||||||
const auto afterstr = get_string_or(params, ":after");
|
const auto afterstr = get_string_or(params, ":after");
|
||||||
const auto tstampstr = get_string_or(params, ":tstamp");
|
const auto tstampstr = get_string_or(params, ":tstamp");
|
||||||
|
const auto maxnum = get_int_or(params, ":maxnum", 0 /*unlimited*/);
|
||||||
|
|
||||||
const auto after{afterstr.empty() ? 0 :
|
const auto after{afterstr.empty() ? 0 :
|
||||||
parse_date_time(afterstr, true).value_or(0)};
|
parse_date_time(afterstr, true).value_or(0)};
|
||||||
|
@ -550,18 +552,20 @@ Server::Private::contacts_handler(const Parameters& params)
|
||||||
contact.add_prop(":address",
|
contact.add_prop(":address",
|
||||||
Sexp::make_string(ci.display_name(true/*encode-if-needed*/)));
|
Sexp::make_string(ci.display_name(true/*encode-if-needed*/)));
|
||||||
contact.add_prop(":rank", Sexp::make_number(rank));
|
contact.add_prop(":rank", Sexp::make_number(rank));
|
||||||
|
auto contacts_sexp{Sexp::make_list(std::move(contact))};
|
||||||
contacts.add(Sexp::make_list(std::move(contact)));
|
contacts_sexp.formatting_opts |= Sexp::FormattingOptions::SplitList;
|
||||||
});
|
contacts.add(std::move(contacts_sexp));
|
||||||
|
}, static_cast<size_t>(maxnum));
|
||||||
|
|
||||||
Sexp::List seq;
|
Sexp::List seq;
|
||||||
seq.add_prop(":contacts", Sexp::make_list(std::move(contacts)));
|
seq.add_prop(":contacts", Sexp::make_list(std::move(contacts)));
|
||||||
seq.add_prop(":tstamp",
|
seq.add_prop(":tstamp",
|
||||||
Sexp::make_string(format("%" G_GINT64_FORMAT,
|
Sexp::make_string(format("%" G_GINT64_FORMAT,
|
||||||
g_get_monotonic_time())));
|
g_get_monotonic_time())));
|
||||||
|
|
||||||
/* dump the contacts cache as a giant sexp */
|
/* dump the contacts cache as a giant sexp */
|
||||||
g_debug("sending %d of %zu contact(s)", rank, store().contacts_cache().size());
|
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 */
|
/* 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.
|
// construct sexp for a single header.
|
||||||
auto qm{mi.query_match()};
|
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
|
// we output up-to-batch-size lists of messages. It's much
|
||||||
// faster (on the emacs side) to handle such batches than single
|
// faster (on the emacs side) to handle such batches than single
|
||||||
// headers.
|
// headers.
|
||||||
|
@ -804,9 +810,11 @@ Server::Private::index_handler(const Parameters& params)
|
||||||
indexer().start(conf);
|
indexer().start(conf);
|
||||||
while (indexer().is_running()) {
|
while (indexer().is_running()) {
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,17 +24,32 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <utils/mu-sexp.hh>
|
#include <utils/mu-sexp.hh>
|
||||||
|
#include <utils/mu-utils.hh>
|
||||||
#include <mu-store.hh>
|
#include <mu-store.hh>
|
||||||
|
|
||||||
namespace Mu {
|
namespace Mu {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Implements the mu server, as used by mu4e.
|
* @brief Implements the mu server, as used by mu4e.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class Server {
|
class Server {
|
||||||
public:
|
public:
|
||||||
using Output = std::function<void(Sexp&& sexp, bool flush)>;
|
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<void(Sexp&& sexp, OutputFlags flags)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new server
|
* Construct a new server
|
||||||
|
@ -63,6 +78,9 @@ private:
|
||||||
struct Private;
|
struct Private;
|
||||||
std::unique_ptr<Private> priv_;
|
std::unique_ptr<Private> priv_;
|
||||||
};
|
};
|
||||||
|
MU_ENABLE_BITOPS(Server::OutputFlags);
|
||||||
|
|
||||||
} // namespace Mu
|
} // namespace Mu
|
||||||
|
|
||||||
|
|
||||||
#endif /* MU_SERVER_HH__ */
|
#endif /* MU_SERVER_HH__ */
|
||||||
|
|
|
@ -188,6 +188,9 @@ Sexp::to_sexp_string() const
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
sstrm << ')';
|
sstrm << ')';
|
||||||
|
|
||||||
|
if (any_of(formatting_opts & FormattingOptions::SplitList))
|
||||||
|
sstrm << '\n';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type::String:
|
case Type::String:
|
||||||
|
@ -225,6 +228,8 @@ Sexp::to_json_string() const
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
sstrm << "}";
|
sstrm << "}";
|
||||||
|
if (any_of(formatting_opts & FormattingOptions::SplitList))
|
||||||
|
sstrm << '\n';
|
||||||
} else { // other lists become arrays.
|
} else { // other lists become arrays.
|
||||||
sstrm << '[';
|
sstrm << '[';
|
||||||
bool first{true};
|
bool first{true};
|
||||||
|
@ -233,6 +238,8 @@ Sexp::to_json_string() const
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
sstrm << ']';
|
sstrm << ']';
|
||||||
|
if (any_of(formatting_opts & FormattingOptions::SplitList))
|
||||||
|
sstrm << '\n';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,6 +342,14 @@ struct Sexp {
|
||||||
return is_prop_list(list().begin() + 1, list().end());
|
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:
|
private:
|
||||||
Sexp(Type typearg, std::string&& valuearg) : type_{typearg}, value_{std::move(valuearg)} {
|
Sexp(Type typearg, std::string&& valuearg) : type_{typearg}, value_{std::move(valuearg)} {
|
||||||
if (is_list())
|
if (is_list())
|
||||||
|
@ -395,11 +403,21 @@ static inline std::ostream&
|
||||||
operator<<(std::ostream& os, Sexp::Type id)
|
operator<<(std::ostream& os, Sexp::Type id)
|
||||||
{
|
{
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case Sexp::Type::List: os << "list"; break;
|
case Sexp::Type::List:
|
||||||
case Sexp::Type::String: os << "string"; break;
|
os << "list";
|
||||||
case Sexp::Type::Number: os << "number"; break;
|
break;
|
||||||
case Sexp::Type::Symbol: os << "symbol"; break;
|
case Sexp::Type::String:
|
||||||
case Sexp::Type::Empty: os << "empty"; break;
|
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");
|
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));
|
os << Sexp::make_list(Sexp::List(sexp));
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
MU_ENABLE_BITOPS(Sexp::FormattingOptions);
|
||||||
|
|
||||||
} // namespace Mu
|
} // namespace Mu
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ install_sig_handler(void)
|
||||||
for (i = 0; i != G_N_ELEMENTS(sigs); ++i)
|
for (i = 0; i != G_N_ELEMENTS(sigs); ++i)
|
||||||
if (sigaction(sigs[i], &action, NULL) != 0)
|
if (sigaction(sigs[i], &action, NULL) != 0)
|
||||||
g_critical("set sigaction for %d failed: %s",
|
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
|
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()};
|
const auto str{sexp.to_sexp_string()};
|
||||||
|
|
||||||
cookie(str.size() + 1);
|
cookie(str.size() + 1);
|
||||||
if (G_UNLIKELY(::puts(str.c_str()) < 0)) {
|
if (G_UNLIKELY(::puts(str.c_str()) < 0)) {
|
||||||
g_critical("failed to write output '%s'", str.c_str());
|
g_critical("failed to write output '%s'", str.c_str());
|
||||||
::raise(SIGTERM); /* terminate ourselves */
|
::raise(SIGTERM); /* terminate ourselves */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flush)
|
if (any_of(flags & Server::OutputFlags::Flush))
|
||||||
std::fflush(stdout);
|
std::fflush(stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +109,8 @@ report_error(const Mu::Error& err) noexcept
|
||||||
e.add_prop(":error", Sexp::make_number(static_cast<size_t>(err.code())));
|
e.add_prop(":error", Sexp::make_number(static_cast<size_t>(err.code())));
|
||||||
e.add_prop(":message", Sexp::make_string(err.what()));
|
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
|
MuError
|
||||||
|
@ -113,15 +120,15 @@ try {
|
||||||
Server server{store, output_sexp_stdout};
|
Server server{store, output_sexp_stdout};
|
||||||
|
|
||||||
g_message("created server with store @ %s; maildir @ %s; debug-mode %s",
|
g_message("created server with store @ %s; maildir @ %s; debug-mode %s",
|
||||||
store.properties().database_path.c_str(),
|
store.properties().database_path.c_str(),
|
||||||
store.properties().root_maildir.c_str(),
|
store.properties().root_maildir.c_str(),
|
||||||
opts->debug ? "yes" : "no");
|
opts->debug ? "yes" : "no");
|
||||||
|
|
||||||
tty = ::isatty(::fileno(stdout));
|
tty = ::isatty(::fileno(stdout));
|
||||||
|
|
||||||
const auto eval = std::string{opts->commands ? "(help :full t)"
|
const auto eval = std::string{opts->commands ? "(help :full t)"
|
||||||
: opts->eval ? opts->eval
|
: opts->eval ? opts->eval
|
||||||
: ""};
|
: ""};
|
||||||
if (!eval.empty()) {
|
if (!eval.empty()) {
|
||||||
server.invoke(eval);
|
server.invoke(eval);
|
||||||
return MU_OK;
|
return MU_OK;
|
||||||
|
|
Loading…
Reference in New Issue