Compare commits
10 Commits
c207921cfa
...
4bda2718ac
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bda2718ac | |||
| 44a16ff4e2 | |||
| d66a29cf5e | |||
| a69e6fad1d | |||
| 02a0dc4333 | |||
| 9a2d481fc3 | |||
| d5a0fce4cf | |||
| 81ff303d2e | |||
| 6b89f4abae | |||
| 3b7e5507f7 |
28
NEWS.org
28
NEWS.org
@ -293,23 +293,31 @@
|
||||
property ~:hide-if-no-unread~, which hides the maildir/bookmark from the
|
||||
main-view if there are no unread messages which the corresponding query.
|
||||
|
||||
- 1.12.12: it now possible to create Emacs bookmarks for both messages
|
||||
- 1.12.12: it now possible to create Emacs bookmarks for both messages
|
||||
(default) and queries. See the new variable ~mu4e-emacs-bookmark-policy~.
|
||||
|
||||
- 1.12.12: ~mu4e-get-mail-command~ which specifies the shell command to use for
|
||||
getting mail, can now also be a function return that shell-command. This
|
||||
makes it easier to use different shell commands in different situations.
|
||||
- 1.12.12: ~mu4e-get-mail-command~ which specifies the shell command to use
|
||||
for getting mail, can now also be a function return that shell-command.
|
||||
This makes it easier to use different shell commands in different
|
||||
situations.
|
||||
|
||||
- 1.12.13: with an SCM-enabled ~mu~, you can set ~mu4e-mu-scm-server~ to non-nil
|
||||
and connect to the SCM REPL with ~M-x mu4e-mu-scm-repl~ after restart (see
|
||||
their docstrings for details)
|
||||
|
||||
*** scm
|
||||
|
||||
- 1.12.12: add new guile/scheme binding in ~scm/~. These are to replace the
|
||||
long-deprecated ~guile/~ bindings. For now, this is all rather new and
|
||||
experimental, but the basics are there.
|
||||
- 1.12.12: add new guile/scheme binding in ~scm/~. These are to replace the
|
||||
long-deprecated ~guile/~ bindings. For now, this is all rather new and
|
||||
experimental, but the basics are there.
|
||||
|
||||
This requires a slightly newer gmime (3.2.8?) than the one ~mu~ requires.
|
||||
This requires a slightly newer gmime (3.2.8?) than the one ~mu~ requires.
|
||||
|
||||
- 1.12.13: add the ~--listen~ flag for ~mu scm~, to start a REPL on a Unix domain
|
||||
sockets. See the reference manual for details.
|
||||
- 1.12.13: add the ~--listen~ flag for ~mu scm~ and ~mu server~, to start a REPL
|
||||
on a Unix domain socket. See the reference manual for details.
|
||||
|
||||
- 1.12.13: ~mu scm~ also gained support for labels and logging; furthermore,
|
||||
~mfind~ was made much faster.
|
||||
|
||||
*** Contributors
|
||||
|
||||
|
||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1756125398,
|
||||
"narHash": "sha256-XexyKZpf46cMiO5Vbj+dWSAXOnr285GHsMch8FBoHbc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3b9f00d7a7bf68acd4c4abb9d43695afb04e03a5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
72
flake.nix
Normal file
72
flake.nix
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
description = "mu and mu4e email package for emacs";
|
||||
|
||||
inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; };
|
||||
|
||||
outputs = { self, nixpkgs }: {
|
||||
|
||||
packages.x86_64-linux.default =
|
||||
with import nixpkgs { system = "x86_64-linux"; };
|
||||
stdenv.mkDerivation {
|
||||
name = "mu4e";
|
||||
src = self;
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
meson
|
||||
ninja
|
||||
texinfo
|
||||
#installShellFiles
|
||||
emacs
|
||||
python3
|
||||
#makeWrapper # need?
|
||||
cmake
|
||||
cld2
|
||||
];
|
||||
|
||||
buildInputs = [ glib gmime3 xapian ];
|
||||
|
||||
mesonFlags = [
|
||||
# "--debug"
|
||||
"-Dguile=disabled"
|
||||
"-Dreadline=disabled"
|
||||
"-Demacs=${emacs}/bin/emacs"
|
||||
];
|
||||
|
||||
# preConfigure = ''
|
||||
# echo "=== DEBUG: Environment before meson configure ==="
|
||||
# echo "PWD: $(pwd)"
|
||||
# echo "Python version:"
|
||||
# python3 --version || echo "python3 not available"
|
||||
# echo "Python path:"
|
||||
# #which python3 || echo "python3 not in PATH"
|
||||
# echo "PATH: $PATH"
|
||||
# echo "Meson version:"
|
||||
# meson --version
|
||||
# echo "Available files in current directory:"
|
||||
# ls -la
|
||||
# echo "Checking for Python scripts:"
|
||||
# find . -name "*.py" -type f | head -10
|
||||
# echo "=============================================="
|
||||
# '';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Email indexer and Emacs client (mu4e)";
|
||||
longDescription = ''
|
||||
mu is a tool for dealing with email messages stored in the
|
||||
Maildir-format. mu's purpose in life is to help you to quickly find the
|
||||
messages you need; in addition, it allows you to view messages, extract
|
||||
attachments, create new maildirs, and so on.
|
||||
|
||||
mu4e is an Emacs-based e-mail client that uses mu as its back-end.
|
||||
'';
|
||||
license = licenses.gpl3Plus;
|
||||
homepage = "https://www.djcbsoftware.nl/code/mu/";
|
||||
changelog = "https://github.com/djcb/mu/blob/v${version}/NEWS.org";
|
||||
maintainers = with maintainers; [ antono chvp peterhoeg ];
|
||||
platforms = platforms.unix;
|
||||
mainProgram = "mu";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@ -132,7 +132,8 @@ struct Server::Private {
|
||||
{}
|
||||
|
||||
~Private() {
|
||||
indexer().stop();
|
||||
if (have_indexer_)
|
||||
indexer().stop();
|
||||
if (!tmp_dir_.empty())
|
||||
remove_directory(tmp_dir_);
|
||||
}
|
||||
@ -145,7 +146,7 @@ struct Server::Private {
|
||||
// acccessors
|
||||
Store& store() { return store_; }
|
||||
const Store& store() const { return store_; }
|
||||
Indexer& indexer() { return store().indexer(); }
|
||||
Indexer& indexer() { have_indexer_ = true; return store().indexer(); }
|
||||
void do_index(const Indexer::Config& conf);
|
||||
//CommandMap& command_map() const { return command_map_; }
|
||||
|
||||
@ -212,6 +213,8 @@ private:
|
||||
const CommandHandler command_handler_;
|
||||
std::atomic<bool> keep_going_{};
|
||||
std::string tmp_dir_;
|
||||
|
||||
bool have_indexer_{};
|
||||
};
|
||||
|
||||
static void
|
||||
@ -1021,15 +1024,19 @@ Server::Private::ping_handler(const Command& cmd)
|
||||
for (auto&& addr : store().config().get<Config::Id::PersonalAddresses>())
|
||||
addrs.add(addr);
|
||||
|
||||
output_sexp(Sexp()
|
||||
.put_props(":pong", "mu")
|
||||
.put_props(":props",
|
||||
Sexp().put_props(
|
||||
":version", VERSION,
|
||||
":personal-addresses", std::move(addrs),
|
||||
":database-path", store().path(),
|
||||
":root-maildir", store().root_maildir(),
|
||||
":doccount", storecount)));
|
||||
Sexp props = Sexp().put_props(
|
||||
":version", VERSION,
|
||||
":personal-addresses", std::move(addrs),
|
||||
":database-path", store().path(),
|
||||
":root-maildir", store().root_maildir(),
|
||||
":doccount", storecount);
|
||||
|
||||
if (!options_.socket_path.empty())
|
||||
props.put_props(
|
||||
":scm-socket-path", options_.socket_path);
|
||||
|
||||
output_sexp(Sexp().put_props(":pong", "mu")
|
||||
.put_props(":props", std::move(props)));
|
||||
}
|
||||
|
||||
void
|
||||
@ -1107,7 +1114,9 @@ Server::Private::view_mark_as_read(Store::Id docid, Message&& msg, bool rename)
|
||||
if (rename)
|
||||
move_opts |= Store::MoveOptions::ChangeName;
|
||||
|
||||
const auto ids{Store::id_vec(unwrap(store().move_message(docid, {}, nflags, move_opts)))};
|
||||
const auto ids{Store::id_vec(
|
||||
unwrap(store().move_message(docid, {},
|
||||
nflags, move_opts)))};
|
||||
for (auto&& [id, moved_msg]: store().find_messages(ids))
|
||||
output(mu_format("({} {})", id == docid ? ":view" : ":update",
|
||||
msg_sexp_str(moved_msg, id, {})));
|
||||
@ -1142,7 +1151,10 @@ Server::Private::view_handler(const Command& cmd)
|
||||
|
||||
Server::Server(Store& store, const Server::Options& opts, Server::Output output)
|
||||
: priv_{std::make_unique<Private>(store, opts, output)}
|
||||
{}
|
||||
{
|
||||
mu_message("created server with store @ {}; maildir @ {}",
|
||||
store.path(), store.root_maildir());
|
||||
}
|
||||
|
||||
Server::~Server() = default;
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ public:
|
||||
|
||||
struct Options {
|
||||
bool allow_temp_file; /**< temp file optimization allowed? */
|
||||
std::string socket_path; /**< Socket path or empty */
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -61,10 +61,16 @@ directly through the emacs process input/output. This is noticeably faster for
|
||||
commands with a lot of output, esp. when the the temp-file uses a in-memory
|
||||
file-system.
|
||||
|
||||
** --listen
|
||||
If set, the server starts an SCM REPL as well, which listens on a Unix domain
|
||||
socket. This corresponds to the *--listen* options for *mu scm**.
|
||||
|
||||
The store object (including the Xapian database) is shared between the server and the REPL.
|
||||
|
||||
* PERFORMANCE
|
||||
|
||||
As an indication for the relative performance, we can simulate something ~mu4e~
|
||||
does; we take overall time of 50 such requests:
|
||||
does. We take the overall time of 50 such requests:
|
||||
|
||||
#+begin_src sh
|
||||
time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-related t :threads t :maxnum 50000)' >/dev/null
|
||||
@ -78,7 +84,6 @@ time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-r
|
||||
| 1.10 | 5.7s |
|
||||
| 1.11 (master) | 2.8s |
|
||||
|
||||
|
||||
#+include: "muhome.inc" :minlevel 2
|
||||
|
||||
#+include: "common-options.inc" :minlevel 1
|
||||
@ -87,4 +92,5 @@ time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-r
|
||||
|
||||
* SEE ALSO
|
||||
|
||||
{{{man-link(mu,1)}}}
|
||||
{{{man-link(mu,1)}}},
|
||||
{{{man-link(mu-scm,1)}}}
|
||||
|
||||
13
meson.build
13
meson.build
@ -165,6 +165,19 @@ else
|
||||
message('no wordexp, no command-line option expansion')
|
||||
endif
|
||||
|
||||
pthread_setname_np_code = '''
|
||||
#include <pthread.h>
|
||||
int main() { return pthread_setname_np(pthread_self(), "test"); }
|
||||
'''
|
||||
pthread_setname_np_check = cxx.compiles(
|
||||
pthread_setname_np_code,
|
||||
name: 'pthread_setname_np check',
|
||||
args: '-D_GNU_SOURCE'
|
||||
)
|
||||
if pthread_setname_np_check
|
||||
config_h_data.set('HAVE_PTHREAD_SETNAME_NP', 1)
|
||||
endif
|
||||
|
||||
if not get_option('tests').disabled()
|
||||
# only needed for tests
|
||||
cp=find_program('cp')
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
** Copyright (C) 2020-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
** Copyright (C) 2020-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
@ -29,6 +29,10 @@
|
||||
#include "mu-cmd.hh"
|
||||
#include "mu-server.hh"
|
||||
|
||||
#if BUILD_SCM
|
||||
#include "scm/mu-scm.hh"
|
||||
#endif/*BUILD_SCM*/
|
||||
|
||||
#include "utils/mu-utils.hh"
|
||||
#include "utils/mu-readline.hh"
|
||||
|
||||
@ -60,6 +64,8 @@ install_sig_handler()
|
||||
}
|
||||
|
||||
/*
|
||||
* djcb@djcbsoftware.nl
|
||||
*
|
||||
* Markers for/after the length cookie that precedes the expression we write to
|
||||
* output. We use octal 376, 377 (ie, 0xfe, 0xff) as they will never occur in
|
||||
* utf8
|
||||
@ -102,6 +108,46 @@ report_error(const Mu::Error& err) noexcept
|
||||
Server::OutputFlags::Flush);
|
||||
}
|
||||
|
||||
static void
|
||||
maybe_setup_readline(const Mu::Options& opts)
|
||||
{
|
||||
tty = ::isatty(::fileno(stdout));
|
||||
|
||||
// Note, the readline stuff is inactive unless on a tty.
|
||||
const auto histpath{opts.runtime_path(RuntimePath::Cache) + "/history"};
|
||||
setup_readline(histpath, 50);
|
||||
}
|
||||
|
||||
// get the SCM socket path we're listening on, or empty if not listening
|
||||
static std::string
|
||||
maybe_listen_path(const Mu::Store& store, const Mu::Options& opts)
|
||||
{
|
||||
#ifdef BUILD_SCM
|
||||
if (!opts.scm.socket_path)
|
||||
return {};
|
||||
const auto res = Mu::Scm::run(store, opts, false/*!block*/);
|
||||
if (!res) {
|
||||
mu_warning("failed to start scm socket: {}", res.error().what());
|
||||
return {};
|
||||
} else
|
||||
return *opts.scm.socket_path;
|
||||
#endif /*BUILD_SCM*/
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool
|
||||
maybe_eval(Server& server, const Mu::Options& opts)
|
||||
{
|
||||
const auto eval = std::string{opts.server.commands ?
|
||||
"(help :full t)" : opts.server.eval};
|
||||
|
||||
if (!eval.empty()) {
|
||||
server.invoke(eval);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
Result<void>
|
||||
Mu::mu_cmd_server(const Mu::Options& opts) try {
|
||||
|
||||
@ -110,31 +156,23 @@ Mu::mu_cmd_server(const Mu::Options& opts) try {
|
||||
if (!store)
|
||||
return Err(store.error());
|
||||
|
||||
Server::Options sopts{};
|
||||
sopts.allow_temp_file = opts.server.allow_temp_file;
|
||||
// empty when we're not listening
|
||||
const auto socket_path = maybe_listen_path(*store, opts);
|
||||
|
||||
Server::Options sopts{opts.server.allow_temp_file, socket_path};
|
||||
Server server{*store, sopts, output_stdout};
|
||||
mu_message("created server with store @ {}; maildir @ {}; debug-mode {};"
|
||||
"readline: {}",
|
||||
store->path(), store->root_maildir(),
|
||||
opts.debug ? "yes" : "no",
|
||||
have_readline() ? "yes" : "no");
|
||||
|
||||
tty = ::isatty(::fileno(stdout));
|
||||
const auto eval = std::string{opts.server.commands ? "(help :full t)" : opts.server.eval};
|
||||
if (!eval.empty()) {
|
||||
server.invoke(eval);
|
||||
if (maybe_eval(server, opts))
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// Note, the readline stuff is inactive unless on a tty.
|
||||
const auto histpath{opts.runtime_path(RuntimePath::Cache) + "/history"};
|
||||
setup_readline(histpath, 50);
|
||||
|
||||
maybe_setup_readline(opts);
|
||||
install_sig_handler();
|
||||
mu_println(";; Welcome to the " PACKAGE_STRING " command-server{}\n"
|
||||
";; Use (help) to get a list of commands, (quit) to quit.",
|
||||
|
||||
mu_println(";; Welcome to the " PACKAGE_STRING " command-server{}",
|
||||
opts.debug ? " (debug-mode)" : "");
|
||||
if (!socket_path.empty())
|
||||
mu_println(";; SCM socket listening on {}", socket_path);
|
||||
mu_println(";; Use (help) to get a list of commands, (quit) to quit.");
|
||||
|
||||
bool do_quit{};
|
||||
while (!MuTerminate && !do_quit) {
|
||||
@ -151,7 +189,6 @@ Mu::mu_cmd_server(const Mu::Options& opts) try {
|
||||
mu_message ("shutting down due to signal {}", MuTerminate.load());
|
||||
|
||||
shutdown_readline();
|
||||
|
||||
return Ok();
|
||||
|
||||
} catch (const Error& er) { /* note: user-level error, "OK" for mu */
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
** Copyright (C) 2010-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
** Copyright (C) 2010-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** This program is free software; you can redistribute it and/or modify it
|
||||
** under the terms of the GNU General Public License as published by the
|
||||
@ -73,7 +73,7 @@ cmd_scm(const Store& store, const Options& opts)
|
||||
return Err(Error::Code::InvalidArgument,
|
||||
"scm/guile is not available in this build");
|
||||
#else
|
||||
return Mu::Scm::run(Mu::Scm::Config{store, opts});
|
||||
return Mu::Scm::run(store, opts, true/*blocking*/);
|
||||
#endif /*BUILD_SCM*/
|
||||
}
|
||||
|
||||
|
||||
@ -616,6 +616,24 @@ sub_remove(CLI::App& sub, Options& opts)
|
||||
->type_name("<files>");
|
||||
}
|
||||
|
||||
static std::string
|
||||
concoct_socket_path()
|
||||
{
|
||||
return join_paths(g_get_user_runtime_dir(),
|
||||
mu_format("mu-scm-{}.sock", ::getpid()));
|
||||
}
|
||||
|
||||
static void
|
||||
add_listen_flag(CLI::App& sub, Options& opts)
|
||||
{
|
||||
sub.add_flag("--listen", opts.scm.listen,
|
||||
"Start SCM REPL on a domain socket");
|
||||
sub.callback([&opts]{
|
||||
if (opts.scm.listen)
|
||||
opts.scm.socket_path = concoct_socket_path();
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
sub_server(CLI::App& sub, Options& opts)
|
||||
{
|
||||
@ -627,18 +645,23 @@ sub_server(CLI::App& sub, Options& opts)
|
||||
sub.add_flag("--allow-temp-file", opts.server.allow_temp_file,
|
||||
"Allow for the temp-file optimization")
|
||||
->excludes("--commands");
|
||||
|
||||
#if BUILD_SCM
|
||||
add_listen_flag(sub, opts);
|
||||
#endif/*BUILD_SCM*/
|
||||
}
|
||||
|
||||
static void
|
||||
sub_scm(CLI::App& sub, Options& opts)
|
||||
{
|
||||
sub.add_flag("--listen", opts.scm.listen,
|
||||
"Start listening on a domain socket");
|
||||
add_listen_flag(sub, opts);
|
||||
|
||||
sub.add_option("script-path", opts.scm.script_path, "Path to script")
|
||||
->type_name("<path>")
|
||||
->excludes("--listen");
|
||||
sub.add_option("script-args", opts.scm.params, "Parameters for script")
|
||||
->type_name("<parameters>");
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
@ -787,7 +810,6 @@ AssocPairs<SubCommand, CommandInfo, Options::SubCommandNum> SubCommandInfos= {{
|
||||
}};
|
||||
|
||||
|
||||
|
||||
static ScriptInfos
|
||||
add_scripts(CLI::App& app, Options& opts)
|
||||
{
|
||||
|
||||
@ -46,13 +46,13 @@ struct Options {
|
||||
/*
|
||||
* general options
|
||||
*/
|
||||
bool quiet; /**< don't give any output */
|
||||
bool debug; /**< log debug-level info */
|
||||
bool version; /**< request mu version */
|
||||
bool log_stderr; /**< log to stderr */
|
||||
bool nocolor; /**< don't use use ansi-colors */
|
||||
bool verbose; /**< verbose output */
|
||||
std::string muhome; /**< alternative mu dir */
|
||||
bool quiet; /**< don't give any output */
|
||||
bool debug; /**< log debug-level info */
|
||||
bool version; /**< request mu version */
|
||||
bool log_stderr; /**< log to stderr */
|
||||
bool nocolor; /**< don't use use ansi-colors */
|
||||
bool verbose; /**< verbose output */
|
||||
std::string muhome; /**< alternative mu dir */
|
||||
|
||||
/**
|
||||
* Whether by default, we should show color
|
||||
@ -260,6 +260,8 @@ struct Options {
|
||||
OptString script_path; /**< Path to script (optional) */
|
||||
StringVec params; /**< Parameters for script (after "--") */
|
||||
bool listen; /**< Whether to start listening on a socket */
|
||||
Option<std::string> socket_path; /**< path for the '--listen'
|
||||
* Unix domain socket */
|
||||
} scm;
|
||||
|
||||
|
||||
|
||||
26
mu/mu.cc
26
mu/mu.cc
@ -77,6 +77,10 @@ handle_result(const Result<void>& res, const Mu::Options& opts)
|
||||
return res.error().exit_code();
|
||||
}
|
||||
|
||||
namespace {
|
||||
Options opts; // it's big, don't want it on the stack
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char* argv[]) try
|
||||
{
|
||||
@ -93,34 +97,36 @@ main(int argc, char* argv[]) try
|
||||
/*
|
||||
* read command-line options
|
||||
*/
|
||||
const auto opts{Options::make(argc, argv)};
|
||||
if (!opts) {
|
||||
output_error(opts.error().what(), !Options::default_no_color());
|
||||
return opts.error().exit_code();
|
||||
} else if (!opts->sub_command) {
|
||||
auto optsres{Options::make(argc, argv)};
|
||||
if (!optsres) {
|
||||
output_error(optsres.error().what(), !Options::default_no_color());
|
||||
return optsres.error().exit_code();
|
||||
} else if (!optsres->sub_command) {
|
||||
// nothing more to do.
|
||||
return 0;
|
||||
}
|
||||
|
||||
opts = std::move(*optsres);
|
||||
|
||||
// setup logging
|
||||
Logger::Options lopts{Logger::Options::None};
|
||||
if (opts->log_stderr)
|
||||
if (opts.log_stderr)
|
||||
lopts |= Logger::Options::StdOutErr;
|
||||
if (opts->debug)
|
||||
if (opts.debug)
|
||||
lopts |= Logger::Options::Debug;
|
||||
if (!!g_getenv("MU_TEST"))
|
||||
lopts |= Logger::Options::File;
|
||||
|
||||
const auto logger{Logger::make(opts->runtime_path(RuntimePath::LogFile), lopts)};
|
||||
const auto logger{Logger::make(opts.runtime_path(RuntimePath::LogFile), lopts)};
|
||||
if (!logger) {
|
||||
output_error(logger.error().what(), !opts->nocolor);
|
||||
output_error(logger.error().what(), !opts.nocolor);
|
||||
return logger.error().exit_code();
|
||||
}
|
||||
|
||||
/*
|
||||
* handle sub command
|
||||
*/
|
||||
return handle_result(mu_cmd_execute(*opts), *opts);
|
||||
return handle_result(mu_cmd_execute(opts), opts);
|
||||
|
||||
// exceptions should have been handled earlier, but catch them here,
|
||||
// just in case...
|
||||
|
||||
@ -26,7 +26,6 @@
|
||||
|
||||
(require 'mu4e-helpers)
|
||||
|
||||
|
||||
;;; Configuration
|
||||
(defcustom mu4e-mu-home nil
|
||||
"Location of an alternate mu home directory.
|
||||
@ -93,6 +92,42 @@ stop/start mu4e."
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defcustom mu4e-mu-scm-server nil
|
||||
"Tell the mu server to start an SCM/Guile REPL.
|
||||
|
||||
This starts the REPL on a Unix domain socket, which can be
|
||||
connected to using various tools. This only works if mu has been
|
||||
built with SCM support (if in doubt, check the output output of
|
||||
\"mu info\" for the line \"scm-support\"); see Info
|
||||
node `(mu-scm) Top' for details on mu-scm.
|
||||
|
||||
The REPL uses the same server instance that mu4e uses.
|
||||
|
||||
Changing this variable only affects the next time the mu4e
|
||||
server is started."
|
||||
:type 'boolean
|
||||
:group 'mu4e
|
||||
:safe 'booleanp)
|
||||
|
||||
(defun mu4e-mu-scm-repl ()
|
||||
"Start a mu-scm REPL using geiser.
|
||||
|
||||
See `mu4e-mu-scm-server' to enable this; and requires the
|
||||
`geiser-guile' package.
|
||||
|
||||
The REPL uses the same server instance that mu4e uses.
|
||||
|
||||
Note: this REPL is not to be confused with the mu REPL as per
|
||||
`mu4e-server-repl'."
|
||||
(interactive)
|
||||
(unless (require 'geiser-guile nil 'noerror)
|
||||
(mu4e-error "geiser-guile not found; please install"))
|
||||
(let ((sock (plist-get (mu4e-server-properties) :scm-socket-path)))
|
||||
(unless sock
|
||||
(mu4e-error "socket-path unavailable"))
|
||||
(when (fboundp 'geiser-connect-local)
|
||||
(geiser-connect-local 'guile sock))))
|
||||
|
||||
;; Cached data
|
||||
(defvar mu4e-maildir-list)
|
||||
|
||||
@ -467,6 +502,7 @@ As per issue #2198."
|
||||
`(,(when mu4e-mu-debug "--debug")
|
||||
"server"
|
||||
,(when mu4e-mu-allow-temp-file "--allow-temp-file")
|
||||
,(when mu4e-mu-scm-server "--listen")
|
||||
,(when mu4e-mu-home (format "--muhome=%s" mu4e-mu-home)))))
|
||||
|
||||
(defun mu4e--version-check ()
|
||||
@ -491,12 +527,15 @@ As per issue #2198."
|
||||
(mu4e-message "Found mu version %s" version)))))
|
||||
|
||||
(defun mu4e-server-repl ()
|
||||
"Start a mu4e-server repl.
|
||||
"Start a mu4e-server REPL.
|
||||
|
||||
This is meant for debugging/testing - the repl is designed for
|
||||
This is meant for debugging/testing - the REPL is designed for
|
||||
machines, not for humans.
|
||||
|
||||
You cannot run the repl when mu4e is running (or vice-versa)."
|
||||
You cannot run the REPL when mu4e is running (or vice-versa).
|
||||
|
||||
Not to be confused with the SCM/Guile REPL, as per
|
||||
`mu4e-mu-scm-server'."
|
||||
(interactive)
|
||||
(if (mu4e-running-p)
|
||||
(mu4e-error "Cannot run repl when mu4e is running")
|
||||
|
||||
@ -171,14 +171,13 @@ subr_cc_message_parts(SCM message_scm) try {
|
||||
auto mime_part{GMIME_PART(part.mime_object().object())};
|
||||
SCM mime_part_scm{to_scm(mime_part)};
|
||||
SCM alist_scm{to_scm(idx, parts[idx])};
|
||||
SCM item{scm_cons(mime_part_scm, alist_scm)};
|
||||
|
||||
parts_scm = scm_append_x(
|
||||
scm_list_2(parts_scm,
|
||||
scm_list_1(
|
||||
scm_cons(mime_part_scm, alist_scm))));
|
||||
parts_scm = scm_cons(item, parts_scm);
|
||||
}
|
||||
|
||||
return parts_scm;
|
||||
return scm_reverse_x(parts_scm, SCM_EOL);
|
||||
|
||||
|
||||
} catch (const ScmError& err) {
|
||||
err.throw_scm();
|
||||
|
||||
@ -21,7 +21,8 @@
|
||||
;; after printing UNIX-CONNECT:<socket-file>\n on stdout
|
||||
(let ((socket-path (getenv "MU_SCM_SOCKET_PATH")))
|
||||
(when socket-path
|
||||
(format #t "UNIX-CONNECT:~a\n" socket-path)
|
||||
(info "listening on domain socket ~a" socket-path)
|
||||
(format #t "~a\n" socket-path)
|
||||
(run-server
|
||||
(make-unix-domain-server-socket #:path socket-path))))
|
||||
|
||||
|
||||
@ -127,7 +127,8 @@ to_sort_field_id(SCM field, const char *func, int pos)
|
||||
|
||||
const auto sym{from_scm<std::string>(scm_symbol_to_string(field), func, pos)};
|
||||
if (const auto field_opt{field_from_name(sym)}; !field_opt) {
|
||||
throw ScmError{ScmError::Id::WrongType, func, pos, field, "sort-field-symbol"};
|
||||
throw ScmError{ScmError::Id::WrongType, func, pos, field,
|
||||
"sort-field-symbol"};
|
||||
} else
|
||||
return field_opt->id;
|
||||
}
|
||||
@ -144,7 +145,8 @@ subr_cc_store_mfind(SCM store_scm, SCM query_scm, SCM related_scm, SCM skip_dups
|
||||
const auto skip_dups(from_scm<bool>(skip_dups_scm, func, 4));
|
||||
|
||||
if (!scm_is_false(sort_field_scm) && !scm_is_symbol(sort_field_scm))
|
||||
throw ScmError{ScmError::Id::WrongType, func, 5, sort_field_scm, "#f or sort-field-symbol"};
|
||||
throw ScmError{ScmError::Id::WrongType, func, 5, sort_field_scm,
|
||||
"#f or sort-field-symbol"};
|
||||
|
||||
const auto sort_field_id = to_sort_field_id(sort_field_scm, func, 5);
|
||||
const auto reverse(from_scm<bool>(reverse_scm, func, 6));
|
||||
|
||||
255
scm/mu-scm.cc
255
scm/mu-scm.cc
@ -16,23 +16,31 @@
|
||||
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
#include "mu-scm.hh"
|
||||
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mu-utils.hh"
|
||||
#include "config.h"
|
||||
|
||||
#include "mu-scm-types.hh"
|
||||
|
||||
#ifdef HAVE_PTHREAD_SETNAME_NP
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
using namespace Mu;
|
||||
using namespace Mu::Scm;
|
||||
|
||||
namespace {
|
||||
static const Mu::Scm::Config *config{};
|
||||
static SCM mu_mod; // The mu module
|
||||
SCM mu_mod; // The mu module
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,14 +65,45 @@ init_options(const Options& opts)
|
||||
}
|
||||
|
||||
static void
|
||||
init_module_mu(void* _data)
|
||||
init_misc()
|
||||
{
|
||||
init_options(config->options);
|
||||
init_store(config->store);
|
||||
init_message();
|
||||
init_mime();
|
||||
scm_define(make_symbol("level-critical"), to_scm(G_LOG_LEVEL_CRITICAL));
|
||||
scm_define(make_symbol("level-warning"), to_scm(G_LOG_LEVEL_WARNING));
|
||||
scm_define(make_symbol("level-info"), to_scm(G_LOG_LEVEL_INFO));
|
||||
scm_define(make_symbol("level-debug"), to_scm(G_LOG_LEVEL_DEBUG));
|
||||
}
|
||||
|
||||
static SCM
|
||||
subr_cc_log(SCM level_scm, SCM str_scm) try {
|
||||
constexpr auto func{"cc-log"};
|
||||
|
||||
const auto level{static_cast<GLogLevelFlags>(from_scm<int>(level_scm, func, 1))};
|
||||
if (level != G_LOG_LEVEL_CRITICAL && level != G_LOG_LEVEL_WARNING &&
|
||||
level != G_LOG_LEVEL_INFO && level != G_LOG_LEVEL_DEBUG)
|
||||
throw ScmError{ScmError::Id::WrongType, func, 1, level_scm, "level"};
|
||||
|
||||
const auto str{from_scm<std::string>(str_scm, func, 2)};
|
||||
|
||||
g_log("mu-scm", level, "%s", str.c_str());
|
||||
|
||||
return SCM_UNSPECIFIED;
|
||||
|
||||
} catch (const ScmError& err) {
|
||||
err.throw_scm();
|
||||
}
|
||||
|
||||
static void
|
||||
init_subrs()
|
||||
{
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||||
scm_c_define_gsubr("cc-log", 2/*req*/, 0/*opt*/, 0/*rst*/,
|
||||
reinterpret_cast<scm_t_subr>(subr_cc_log));
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const Result<std::string>
|
||||
make_mu_scm_path(const std::string& fname) {
|
||||
|
||||
@ -84,23 +123,20 @@ make_mu_scm_path(const std::string& fname) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
static std::string mu_scm_path;
|
||||
static std::string mu_scm_repl_path;
|
||||
static std::string mu_scm_socket_path;
|
||||
std::string mu_scm_path;
|
||||
std::string mu_scm_repl_path;
|
||||
std::string mu_scm_socket_path;
|
||||
constexpr auto SOCKET_PATH_ENV = "MU_SCM_SOCKET_PATH";
|
||||
using StrVec = std::vector<std::string>;
|
||||
StrVec scm_args;
|
||||
std::thread scm_worker;
|
||||
}
|
||||
|
||||
static Result<void>
|
||||
prepare_run(const Mu::Scm::Config& conf)
|
||||
prepare_run(const Mu::Options& opts)
|
||||
{
|
||||
if (config)
|
||||
return Err(Error{Error::Code::AccessDenied,
|
||||
"already prepared"});
|
||||
config = &conf;
|
||||
|
||||
// do a checks _before_ entering guile, so we get a bit more civilized
|
||||
// error message.
|
||||
|
||||
if (const auto path = make_mu_scm_path("mu-scm.scm"); path)
|
||||
mu_scm_path = *path;
|
||||
else
|
||||
@ -111,8 +147,8 @@ prepare_run(const Mu::Scm::Config& conf)
|
||||
else
|
||||
return Err(path.error());
|
||||
|
||||
if (config->options.scm.script_path) {
|
||||
const auto path{config->options.scm.script_path->c_str()};
|
||||
if (opts.scm.script_path) {
|
||||
const auto path{opts.scm.script_path->c_str()};
|
||||
if (const auto res = ::access(path, R_OK); res != 0) {
|
||||
return Err(Error::Code::InvalidArgument,
|
||||
"cannot read '{}': {}", path, ::strerror(errno));
|
||||
@ -122,71 +158,130 @@ prepare_run(const Mu::Scm::Config& conf)
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// make a unique unix-socket path
|
||||
static std::string
|
||||
maybe_set_uds_path(bool set)
|
||||
static void
|
||||
prepare_script(const Options& opts, StrVec& args)
|
||||
{
|
||||
if (set) {
|
||||
GRand* grand{g_rand_new()};
|
||||
auto path = join_paths(g_get_user_runtime_dir(),
|
||||
mu_format("mu-scm-socket-{:08x}",
|
||||
g_rand_int(grand)));
|
||||
g_rand_free(grand);
|
||||
g_setenv(SOCKET_PATH_ENV, path.c_str(), 1);
|
||||
return path;
|
||||
} else {
|
||||
g_unsetenv(SOCKET_PATH_ENV);
|
||||
return {};
|
||||
static std::string cmd; // keep alive
|
||||
|
||||
// XXX: couldn't get another combination of -l/-s/-e/-c to work
|
||||
// a) invokes `main' with arguments, and
|
||||
// b) exits (rather than drop to a shell)
|
||||
// but, what works is to manually specify (main ....)
|
||||
cmd = "(main " + quote(*opts.scm.script_path);
|
||||
for (const auto& scriptarg : opts.scm.params)
|
||||
cmd += " " + quote(scriptarg);
|
||||
cmd += ")";
|
||||
|
||||
args.emplace_back("-l");
|
||||
args.emplace_back(*opts.scm.script_path);
|
||||
args.emplace_back("-c");
|
||||
args.emplace_back(cmd);
|
||||
}
|
||||
|
||||
static void
|
||||
maybe_remove_socket_path()
|
||||
{
|
||||
struct stat statbuf{};
|
||||
const auto sock{mu_scm_socket_path};
|
||||
|
||||
// opportunistic, so no real warnings, but be careful deleting!
|
||||
|
||||
if (const int res = ::stat(sock.c_str(), &statbuf); res != 0) {
|
||||
mu_debug("can't stat '{}'; err={}", sock, -res);
|
||||
} else if ((statbuf.st_mode & S_IFMT) != S_IFSOCK) {
|
||||
mu_debug("{} is not a socket", sock);
|
||||
} else if (const int ulres = ::unlink(sock.c_str()); ulres != 0) {
|
||||
mu_debug("failed to unlink '{}'; err={}", sock, -ulres);
|
||||
} else {
|
||||
mu_debug("unlinked {}", sock);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Result<void>
|
||||
Mu::Scm::run(const Mu::Scm::Config& conf)
|
||||
static void
|
||||
prepare_shell(const Options& opts, StrVec& args)
|
||||
{
|
||||
if (const auto res = prepare_run(conf); !res)
|
||||
return Err(res.error());
|
||||
// drop us into an interactive shell/repl or start listening on a domain socket.
|
||||
if (opts.scm.listen && opts.scm.socket_path) {
|
||||
mu_scm_socket_path = *opts.scm.socket_path;
|
||||
g_setenv(SOCKET_PATH_ENV, mu_scm_socket_path.c_str(), 1);
|
||||
mu_info("setting up socket-path {}", mu_scm_socket_path);
|
||||
::atexit(maybe_remove_socket_path); //opportunistic cleanup
|
||||
}
|
||||
else
|
||||
g_unsetenv(SOCKET_PATH_ENV);
|
||||
|
||||
scm_boot_guile(0, {}, [](void *data, int argc, char **argv) {
|
||||
mu_mod = scm_c_define_module ("mu", init_module_mu, {});
|
||||
|
||||
std::vector<const char*> args {
|
||||
"mu",
|
||||
"-l", mu_scm_path.c_str(),
|
||||
};
|
||||
std::string cmd;
|
||||
const auto opts{config->options.scm};
|
||||
// if a script-path was specified, run a script
|
||||
if (opts.script_path) {
|
||||
// XXX: couldn't get another combination of -l/-s/-e/-c to work
|
||||
// a) invokes `main' with arguments, and
|
||||
// b) exits (rather than drop to a shell)
|
||||
// but, what works is to manually specify (main ....)
|
||||
cmd = "(main " + quote(*opts.script_path);
|
||||
for (const auto& scriptarg : opts.params)
|
||||
cmd += " " + quote(scriptarg);
|
||||
cmd += ")";
|
||||
for (const auto& arg: {
|
||||
"-l", opts.script_path->c_str(),
|
||||
"-c", cmd.c_str()})
|
||||
args.emplace_back(arg);
|
||||
} else {
|
||||
// otherwise, drop us into an interactive shell/repl
|
||||
// or start listening on a domain socket.
|
||||
mu_scm_socket_path =
|
||||
maybe_set_uds_path(config->options.scm.listen);
|
||||
args.emplace_back("--no-auto-compile");
|
||||
args.emplace_back("-l");
|
||||
args.emplace_back(mu_scm_repl_path.c_str());
|
||||
}
|
||||
/* ahem...*/
|
||||
scm_shell(std::size(args), const_cast<char**>(args.data()));
|
||||
}, {}); // never returns.
|
||||
|
||||
return Ok();
|
||||
args.emplace_back("--no-auto-compile");
|
||||
args.emplace_back("-l");
|
||||
args.emplace_back(mu_scm_repl_path);
|
||||
}
|
||||
|
||||
|
||||
struct ModMuData { const Mu::Store& store; const Mu::Options& opts; };
|
||||
|
||||
static void
|
||||
init_module_mu(void* data)
|
||||
{
|
||||
const ModMuData& conf{*reinterpret_cast<ModMuData*>(data)};
|
||||
|
||||
init_options(conf.opts);
|
||||
init_misc();
|
||||
init_subrs();
|
||||
|
||||
init_store(conf.store);
|
||||
init_message();
|
||||
init_mime();
|
||||
}
|
||||
|
||||
static void
|
||||
run_scm(const Mu::Store& store, const Mu::Options& opts)
|
||||
{
|
||||
static ModMuData mu_data{store, opts};
|
||||
|
||||
scm_boot_guile(0, {},
|
||||
[](auto _data, auto _argc, auto _argv) {
|
||||
mu_mod = scm_c_define_module ("mu", init_module_mu, &mu_data);
|
||||
std::vector<char*> args;
|
||||
std::transform(scm_args.begin(),
|
||||
scm_args.end(), std::back_inserter(args),
|
||||
[&](const std::string& strarg){
|
||||
/* ahem...*/
|
||||
return const_cast<char*>(strarg.c_str());
|
||||
});
|
||||
scm_shell(args.size(), args.data());
|
||||
|
||||
}, {}); // never returns.
|
||||
}
|
||||
|
||||
Result<void>
|
||||
Mu::Scm::run(const Mu::Store& store, const Mu::Options& opts, bool blocking)
|
||||
{
|
||||
if (const auto res = prepare_run(opts); !res)
|
||||
return Err(res.error());
|
||||
|
||||
scm_args = {"mu", "-l", mu_scm_path};
|
||||
|
||||
// do env stuff _before_ starting guile / threads.
|
||||
if (opts.scm.script_path)
|
||||
prepare_script(opts, scm_args);
|
||||
else
|
||||
prepare_shell(opts, scm_args);
|
||||
|
||||
// in the non-blocking case, we start guile in a
|
||||
// background thread; otherwise it will block.
|
||||
if (!blocking) {
|
||||
auto worker = std::thread([&](){
|
||||
#ifdef HAVE_PTHREAD_SETNAME_NP
|
||||
pthread_setname_np(pthread_self(), "mu-scm");
|
||||
#endif /*HAVE_PTHREAD_SETNAME_NP*/
|
||||
run_scm(store, opts);
|
||||
});
|
||||
worker.detach();
|
||||
} else
|
||||
run_scm(store, opts);
|
||||
|
||||
return Ok();
|
||||
|
||||
}
|
||||
|
||||
|
||||
#ifdef BUILD_TESTS
|
||||
@ -199,7 +294,6 @@ Mu::Scm::run(const Mu::Scm::Config& conf)
|
||||
#include <mu-store.hh>
|
||||
#include "utils/mu-test-utils.hh"
|
||||
|
||||
|
||||
static void
|
||||
test_scm_script()
|
||||
{
|
||||
@ -233,13 +327,8 @@ test_scm_script()
|
||||
Mu::Options opts{};
|
||||
opts.scm.script_path = join_paths(MU_SCM_SRCDIR, "mu-scm-test.scm");
|
||||
|
||||
Mu::Scm::Config scm_conf {
|
||||
/*.store =*/ *store,
|
||||
/*.options =*/ opts
|
||||
};
|
||||
|
||||
{
|
||||
const auto res = Mu::Scm::run(scm_conf);
|
||||
const auto res = Mu::Scm::run(*store, opts, false /*blocks*/);
|
||||
assert_valid_result(res);
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,28 +39,24 @@
|
||||
*
|
||||
*/
|
||||
namespace Mu::Scm {
|
||||
|
||||
/**
|
||||
* Configuration object
|
||||
* Start a guile REPL or program
|
||||
*
|
||||
*/
|
||||
struct Config {
|
||||
const Mu::Store& store;
|
||||
const Options& options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start a guile shell
|
||||
*
|
||||
* Initialize the Scm sub-system, then start a shell or run a script,
|
||||
* Initialize the Scm sub-system, then start a REPL or run a script,
|
||||
* based on the configuration.
|
||||
*
|
||||
* @param conf a Config object
|
||||
* Unless 'blocking' is false or there is some pre-guile error, this
|
||||
* method never returns. If blocking is false, it runs in the
|
||||
* background.
|
||||
*
|
||||
* @param store a Store object
|
||||
* @param opts options
|
||||
* @param blocking whether to block (or run in the background)
|
||||
*
|
||||
* @return Ok() or some error
|
||||
*/
|
||||
Result<void> run(const Config& conf);
|
||||
|
||||
Result<void> run(const Store& store, const Options& opts,
|
||||
bool blocking=true);
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
@ -223,15 +219,18 @@ namespace Mu::Scm {
|
||||
return scm_from_utf8_string(val);
|
||||
else if constexpr (std::is_same_v<Type, std::vector<std::string>>) {
|
||||
SCM lst{SCM_EOL};
|
||||
for (const auto& s: val)
|
||||
lst = scm_append_x(scm_list_2(lst,
|
||||
scm_list_1(to_scm(s))));
|
||||
for (auto it = val.end(); it-- != val.begin();)
|
||||
lst = scm_cons(to_scm(*it), lst);
|
||||
return lst;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Type, bool>)
|
||||
return scm_from_bool(val);
|
||||
else if constexpr (std::is_same_v<Type, size_t>)
|
||||
return scm_from_size_t(val);
|
||||
else if constexpr (std::is_same_v<Type, int>)
|
||||
return scm_from_int(val);
|
||||
else if constexpr (std::is_same_v<Type, GLogLevelFlags>)
|
||||
return scm_from_int(static_cast<int>(val));
|
||||
else if constexpr (std::is_same_v<Type, int64_t>)
|
||||
return scm_from_int64(val);
|
||||
else if constexpr (std::is_same_v<Type, uint64_t>)
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
:use-module (system foreign)
|
||||
:use-module (rnrs bytevectors)
|
||||
:use-module (ice-9 optargs)
|
||||
:use-module (ice-9 format)
|
||||
:use-module (ice-9 binary-ports)
|
||||
#:export (
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@ -100,11 +101,16 @@
|
||||
%options
|
||||
;; %preferences
|
||||
|
||||
;; logging
|
||||
debug
|
||||
info
|
||||
warning
|
||||
critical
|
||||
|
||||
;; helpers
|
||||
string->time
|
||||
time->string))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; Helpers
|
||||
@ -637,3 +643,43 @@ UTC time.
|
||||
(let ((t (if utc? (gmtime time-t) (localtime time-t))))
|
||||
(strftime format t))
|
||||
#f)))
|
||||
|
||||
|
||||
;; Logging to mu's log
|
||||
|
||||
(define (mlog level frmstr . args)
|
||||
"Log to the mu logger.
|
||||
|
||||
LEVEL is the logging-level, a symbol one of:
|
||||
debug, info, warning, critical
|
||||
|
||||
FRMSTR is a format string like `(format)', and the ARGS are the parameters for
|
||||
that format string."
|
||||
(let ((msg (if (null? args)
|
||||
frmstr
|
||||
(apply format #f frmstr args))))
|
||||
(cc-log level msg)))
|
||||
|
||||
(define (info frm . args)
|
||||
"Log a message at info level to the mu logger.
|
||||
FRM is the format string and ARGS are its arguments, see `(format)' for details
|
||||
on the precise format."
|
||||
(apply mlog level-info frm args))
|
||||
|
||||
(define (warning frm . args)
|
||||
"Log a message at warning level to the mu logger.
|
||||
FRM is the format string and ARGS are its arguments, see `(format)' for details
|
||||
on the precise format."
|
||||
(apply mlog level-warning frm args))
|
||||
|
||||
(define (critical frm . args)
|
||||
"Log a message at critical level to the mu logger.
|
||||
FRM is the format string and ARGS are its arguments, see `(format)' for details
|
||||
on the precise format."
|
||||
(apply mlog level-critical frm args))
|
||||
|
||||
(define (debug frm . args)
|
||||
"Log a message at debug level to the mu logger.
|
||||
FRM is the format string and ARGS are its arguments, see `(format)' for details
|
||||
on the precise format."
|
||||
(apply mlog level-debug frm args))
|
||||
|
||||
@ -107,6 +107,7 @@ Indices
|
||||
* Starting the REPL::
|
||||
* Listening on a Unix Domain Socket::
|
||||
* Hooking up with GNU/Emacs and Geiser::
|
||||
* Hooking up with Mu4e::
|
||||
@end menu
|
||||
|
||||
This chapter walks you through the installation and basic setup.
|
||||
@ -183,19 +184,19 @@ REPL over a Unix domain socket, using the @t{--listen} flag.
|
||||
When you start @command{mu scm} with the @t{--listen} flag, it prints a
|
||||
(randomized) UNIX domain socket name and blocks after that; for instance:
|
||||
@example
|
||||
$mu scm --listen
|
||||
UNIX-CONNECT:/run/user/1000/mu-scm-socket-6ef6222e
|
||||
$ mu scm --listen
|
||||
/run/user/1000/mu-scm-15269.sock
|
||||
@end example
|
||||
|
||||
You can connect to this with exernal tools, for instance with @command{socat}:
|
||||
You can connect to this with external tools, for instance with @command{socat}:
|
||||
@example
|
||||
socat - UNIX-CONNECT:/run/user/1000/mu-scm-socket-6ef6222e
|
||||
$ socat - UNIX-CONNECT:/run/user/1000/mu-scm-15269.sock
|
||||
GNU Guile 3.0.9
|
||||
Copyright (C) 1995-2023 Free Software Foundation, Inc.
|
||||
|
||||
Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
|
||||
This program is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `,show c' for details.
|
||||
Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This
|
||||
program is free software, and you are welcome to redistribute it under certain
|
||||
conditions; type `,show c' for details.
|
||||
|
||||
Enter `,help' for help.
|
||||
scheme@@(guile-user)>
|
||||
@ -216,7 +217,7 @@ domain socket, as discussed in @xref{Listening on a Unix Domain Socket}.
|
||||
Assuming you have installed the @t{guile-geiser} package, the following snippet
|
||||
makes that easy:
|
||||
@lisp
|
||||
(require 'guiser-guile)
|
||||
(require 'geiser-guile)
|
||||
|
||||
(defvar mu-scm-listen-command "mu scm --listen"
|
||||
"mu command to start an scm repl listening on a socket.")
|
||||
@ -229,13 +230,32 @@ Connect to mu's scm (guile) interface through Geiser."
|
||||
:name "*mu-scm-repl*"
|
||||
:command `("sh" "-c" ,mu-scm-listen-command)
|
||||
:filter (lambda (_proc chunk)
|
||||
(when (string-match "^UNIX-CONNECT:\\(.*\\)$" chunk)
|
||||
(when (stringng-match "^\\(mu-scm.*\\.sock\\)$" chunk)
|
||||
(geiser-connect-local 'guile (match-string 1 chunk))))))
|
||||
@end lisp
|
||||
|
||||
After evaluating this, you can use @command{M-x mu-scm-geiser-connect} to start
|
||||
the REPL, with all the Geiser bells & whistles.
|
||||
|
||||
@node Hooking up with Mu4e
|
||||
@section Hooking up with Mu4e
|
||||
@cindex Mu4e
|
||||
|
||||
If you use @t{mu4e}, connecting to the SCM server is even easier than with
|
||||
``plain'' Emacs.
|
||||
|
||||
First tell @t{mu4e} to starts it server with with @t{--listen} parameter:
|
||||
@lisp
|
||||
(setq mu4e-mu-scm-server t)
|
||||
@end lisp
|
||||
After that, (re)start @t{mu4e}.
|
||||
|
||||
Now should be able to connect to the REPL using @t{M-x mu4e-mu-scm-repl}. Like
|
||||
@ref{Hooking up with GNU/Emacs and Geiser}, this depends on the @t{geiser-guile}
|
||||
package.
|
||||
|
||||
The SCM instance uses the same database/store instance that @t{mu4e} uses.
|
||||
|
||||
@node Shell
|
||||
@chapter Shell
|
||||
|
||||
@ -919,6 +939,26 @@ their default values.
|
||||
@node Helpers
|
||||
@section Helpers
|
||||
|
||||
@deffn {Scheme Procedure} debug frm . args
|
||||
@end deffn
|
||||
|
||||
@deffn {Scheme Procedure} info frm . args
|
||||
@end deffn
|
||||
|
||||
@deffn {Scheme Procedure} warning frm . args
|
||||
@end deffn
|
||||
|
||||
@deffn {Scheme Procedure} critical frm . args
|
||||
@end deffn
|
||||
|
||||
Log using @t{mu}'s logging facilities at the given level (debug, info, warning
|
||||
or critical). @code{frm} and @code{args} are expected to be a format string and
|
||||
its arguments, respectively, according to Guile's @code{format} function.
|
||||
|
||||
Note: where the log output exactly appears (i.e., log-file, journal, console,
|
||||
nowhere) dependis on the particulars of the system and how mu was started; see
|
||||
the @t{mu (1)} man-page for details on the latter.
|
||||
|
||||
@deffn {Scheme Procedure} string->time timestr [#:utc? (assoc-ref %preferences 'utc?)]
|
||||
@end deffn
|
||||
Convert some ISO-8601-style time-string to a seconds-since-epoch @t{time_t}
|
||||
|
||||
Reference in New Issue
Block a user