Compare commits
10 Commits
c207921cfa
...
4bda2718ac
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bda2718ac | |||
| 44a16ff4e2 | |||
| d66a29cf5e | |||
| a69e6fad1d | |||
| 02a0dc4333 | |||
| 9a2d481fc3 | |||
| d5a0fce4cf | |||
| 81ff303d2e | |||
| 6b89f4abae | |||
| 3b7e5507f7 |
18
NEWS.org
18
NEWS.org
@ -296,9 +296,14 @@
|
|||||||
- 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~.
|
(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
|
- 1.12.12: ~mu4e-get-mail-command~ which specifies the shell command to use
|
||||||
getting mail, can now also be a function return that shell-command. This
|
for getting mail, can now also be a function return that shell-command.
|
||||||
makes it easier to use different shell commands in different situations.
|
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
|
*** scm
|
||||||
|
|
||||||
@ -308,8 +313,11 @@
|
|||||||
|
|
||||||
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
|
- 1.12.13: add the ~--listen~ flag for ~mu scm~ and ~mu server~, to start a REPL
|
||||||
sockets. See the reference manual for details.
|
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
|
*** 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,6 +132,7 @@ struct Server::Private {
|
|||||||
{}
|
{}
|
||||||
|
|
||||||
~Private() {
|
~Private() {
|
||||||
|
if (have_indexer_)
|
||||||
indexer().stop();
|
indexer().stop();
|
||||||
if (!tmp_dir_.empty())
|
if (!tmp_dir_.empty())
|
||||||
remove_directory(tmp_dir_);
|
remove_directory(tmp_dir_);
|
||||||
@ -145,7 +146,7 @@ struct Server::Private {
|
|||||||
// acccessors
|
// acccessors
|
||||||
Store& store() { return store_; }
|
Store& store() { return store_; }
|
||||||
const Store& store() const { 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);
|
void do_index(const Indexer::Config& conf);
|
||||||
//CommandMap& command_map() const { return command_map_; }
|
//CommandMap& command_map() const { return command_map_; }
|
||||||
|
|
||||||
@ -212,6 +213,8 @@ private:
|
|||||||
const CommandHandler command_handler_;
|
const CommandHandler command_handler_;
|
||||||
std::atomic<bool> keep_going_{};
|
std::atomic<bool> keep_going_{};
|
||||||
std::string tmp_dir_;
|
std::string tmp_dir_;
|
||||||
|
|
||||||
|
bool have_indexer_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -1021,15 +1024,19 @@ Server::Private::ping_handler(const Command& cmd)
|
|||||||
for (auto&& addr : store().config().get<Config::Id::PersonalAddresses>())
|
for (auto&& addr : store().config().get<Config::Id::PersonalAddresses>())
|
||||||
addrs.add(addr);
|
addrs.add(addr);
|
||||||
|
|
||||||
output_sexp(Sexp()
|
Sexp props = Sexp().put_props(
|
||||||
.put_props(":pong", "mu")
|
|
||||||
.put_props(":props",
|
|
||||||
Sexp().put_props(
|
|
||||||
":version", VERSION,
|
":version", VERSION,
|
||||||
":personal-addresses", std::move(addrs),
|
":personal-addresses", std::move(addrs),
|
||||||
":database-path", store().path(),
|
":database-path", store().path(),
|
||||||
":root-maildir", store().root_maildir(),
|
":root-maildir", store().root_maildir(),
|
||||||
":doccount", storecount)));
|
":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
|
void
|
||||||
@ -1107,7 +1114,9 @@ Server::Private::view_mark_as_read(Store::Id docid, Message&& msg, bool rename)
|
|||||||
if (rename)
|
if (rename)
|
||||||
move_opts |= Store::MoveOptions::ChangeName;
|
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))
|
for (auto&& [id, moved_msg]: store().find_messages(ids))
|
||||||
output(mu_format("({} {})", id == docid ? ":view" : ":update",
|
output(mu_format("({} {})", id == docid ? ":view" : ":update",
|
||||||
msg_sexp_str(moved_msg, id, {})));
|
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)
|
Server::Server(Store& store, const Server::Options& opts, Server::Output output)
|
||||||
: priv_{std::make_unique<Private>(store, opts, output)}
|
: priv_{std::make_unique<Private>(store, opts, output)}
|
||||||
{}
|
{
|
||||||
|
mu_message("created server with store @ {}; maildir @ {}",
|
||||||
|
store.path(), store.root_maildir());
|
||||||
|
}
|
||||||
|
|
||||||
Server::~Server() = default;
|
Server::~Server() = default;
|
||||||
|
|
||||||
|
|||||||
@ -51,6 +51,7 @@ public:
|
|||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
bool allow_temp_file; /**< temp file optimization allowed? */
|
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
|
commands with a lot of output, esp. when the the temp-file uses a in-memory
|
||||||
file-system.
|
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
|
* PERFORMANCE
|
||||||
|
|
||||||
As an indication for the relative performance, we can simulate something ~mu4e~
|
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
|
#+begin_src sh
|
||||||
time build/mu/mu server --allow-temp-file --eval '(find :query "\"\"" :include-related t :threads t :maxnum 50000)' >/dev/null
|
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.10 | 5.7s |
|
||||||
| 1.11 (master) | 2.8s |
|
| 1.11 (master) | 2.8s |
|
||||||
|
|
||||||
|
|
||||||
#+include: "muhome.inc" :minlevel 2
|
#+include: "muhome.inc" :minlevel 2
|
||||||
|
|
||||||
#+include: "common-options.inc" :minlevel 1
|
#+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
|
* 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')
|
message('no wordexp, no command-line option expansion')
|
||||||
endif
|
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()
|
if not get_option('tests').disabled()
|
||||||
# only needed for tests
|
# only needed for tests
|
||||||
cp=find_program('cp')
|
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
|
** 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
|
** under the terms of the GNU General Public License as published by the
|
||||||
@ -29,6 +29,10 @@
|
|||||||
#include "mu-cmd.hh"
|
#include "mu-cmd.hh"
|
||||||
#include "mu-server.hh"
|
#include "mu-server.hh"
|
||||||
|
|
||||||
|
#if BUILD_SCM
|
||||||
|
#include "scm/mu-scm.hh"
|
||||||
|
#endif/*BUILD_SCM*/
|
||||||
|
|
||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
#include "utils/mu-readline.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
|
* 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
|
* output. We use octal 376, 377 (ie, 0xfe, 0xff) as they will never occur in
|
||||||
* utf8
|
* utf8
|
||||||
@ -102,6 +108,46 @@ report_error(const Mu::Error& err) noexcept
|
|||||||
Server::OutputFlags::Flush);
|
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>
|
Result<void>
|
||||||
Mu::mu_cmd_server(const Mu::Options& opts) try {
|
Mu::mu_cmd_server(const Mu::Options& opts) try {
|
||||||
|
|
||||||
@ -110,31 +156,23 @@ Mu::mu_cmd_server(const Mu::Options& opts) try {
|
|||||||
if (!store)
|
if (!store)
|
||||||
return Err(store.error());
|
return Err(store.error());
|
||||||
|
|
||||||
Server::Options sopts{};
|
// empty when we're not listening
|
||||||
sopts.allow_temp_file = opts.server.allow_temp_file;
|
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};
|
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));
|
if (maybe_eval(server, opts))
|
||||||
const auto eval = std::string{opts.server.commands ? "(help :full t)" : opts.server.eval};
|
|
||||||
if (!eval.empty()) {
|
|
||||||
server.invoke(eval);
|
|
||||||
return Ok();
|
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();
|
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)" : "");
|
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{};
|
bool do_quit{};
|
||||||
while (!MuTerminate && !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());
|
mu_message ("shutting down due to signal {}", MuTerminate.load());
|
||||||
|
|
||||||
shutdown_readline();
|
shutdown_readline();
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|
||||||
} catch (const Error& er) { /* note: user-level error, "OK" for mu */
|
} 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
|
** 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
|
** 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,
|
return Err(Error::Code::InvalidArgument,
|
||||||
"scm/guile is not available in this build");
|
"scm/guile is not available in this build");
|
||||||
#else
|
#else
|
||||||
return Mu::Scm::run(Mu::Scm::Config{store, opts});
|
return Mu::Scm::run(store, opts, true/*blocking*/);
|
||||||
#endif /*BUILD_SCM*/
|
#endif /*BUILD_SCM*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -616,6 +616,24 @@ sub_remove(CLI::App& sub, Options& opts)
|
|||||||
->type_name("<files>");
|
->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
|
static void
|
||||||
sub_server(CLI::App& sub, Options& opts)
|
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,
|
sub.add_flag("--allow-temp-file", opts.server.allow_temp_file,
|
||||||
"Allow for the temp-file optimization")
|
"Allow for the temp-file optimization")
|
||||||
->excludes("--commands");
|
->excludes("--commands");
|
||||||
|
|
||||||
|
#if BUILD_SCM
|
||||||
|
add_listen_flag(sub, opts);
|
||||||
|
#endif/*BUILD_SCM*/
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sub_scm(CLI::App& sub, Options& opts)
|
sub_scm(CLI::App& sub, Options& opts)
|
||||||
{
|
{
|
||||||
sub.add_flag("--listen", opts.scm.listen,
|
add_listen_flag(sub, opts);
|
||||||
"Start listening on a domain socket");
|
|
||||||
sub.add_option("script-path", opts.scm.script_path, "Path to script")
|
sub.add_option("script-path", opts.scm.script_path, "Path to script")
|
||||||
->type_name("<path>")
|
->type_name("<path>")
|
||||||
->excludes("--listen");
|
->excludes("--listen");
|
||||||
sub.add_option("script-args", opts.scm.params, "Parameters for script")
|
sub.add_option("script-args", opts.scm.params, "Parameters for script")
|
||||||
->type_name("<parameters>");
|
->type_name("<parameters>");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -787,7 +810,6 @@ AssocPairs<SubCommand, CommandInfo, Options::SubCommandNum> SubCommandInfos= {{
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static ScriptInfos
|
static ScriptInfos
|
||||||
add_scripts(CLI::App& app, Options& opts)
|
add_scripts(CLI::App& app, Options& opts)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -260,6 +260,8 @@ struct Options {
|
|||||||
OptString script_path; /**< Path to script (optional) */
|
OptString script_path; /**< Path to script (optional) */
|
||||||
StringVec params; /**< Parameters for script (after "--") */
|
StringVec params; /**< Parameters for script (after "--") */
|
||||||
bool listen; /**< Whether to start listening on a socket */
|
bool listen; /**< Whether to start listening on a socket */
|
||||||
|
Option<std::string> socket_path; /**< path for the '--listen'
|
||||||
|
* Unix domain socket */
|
||||||
} scm;
|
} 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();
|
return res.error().exit_code();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Options opts; // it's big, don't want it on the stack
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char* argv[]) try
|
main(int argc, char* argv[]) try
|
||||||
{
|
{
|
||||||
@ -93,34 +97,36 @@ main(int argc, char* argv[]) try
|
|||||||
/*
|
/*
|
||||||
* read command-line options
|
* read command-line options
|
||||||
*/
|
*/
|
||||||
const auto opts{Options::make(argc, argv)};
|
auto optsres{Options::make(argc, argv)};
|
||||||
if (!opts) {
|
if (!optsres) {
|
||||||
output_error(opts.error().what(), !Options::default_no_color());
|
output_error(optsres.error().what(), !Options::default_no_color());
|
||||||
return opts.error().exit_code();
|
return optsres.error().exit_code();
|
||||||
} else if (!opts->sub_command) {
|
} else if (!optsres->sub_command) {
|
||||||
// nothing more to do.
|
// nothing more to do.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts = std::move(*optsres);
|
||||||
|
|
||||||
// setup logging
|
// setup logging
|
||||||
Logger::Options lopts{Logger::Options::None};
|
Logger::Options lopts{Logger::Options::None};
|
||||||
if (opts->log_stderr)
|
if (opts.log_stderr)
|
||||||
lopts |= Logger::Options::StdOutErr;
|
lopts |= Logger::Options::StdOutErr;
|
||||||
if (opts->debug)
|
if (opts.debug)
|
||||||
lopts |= Logger::Options::Debug;
|
lopts |= Logger::Options::Debug;
|
||||||
if (!!g_getenv("MU_TEST"))
|
if (!!g_getenv("MU_TEST"))
|
||||||
lopts |= Logger::Options::File;
|
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) {
|
if (!logger) {
|
||||||
output_error(logger.error().what(), !opts->nocolor);
|
output_error(logger.error().what(), !opts.nocolor);
|
||||||
return logger.error().exit_code();
|
return logger.error().exit_code();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* handle sub command
|
* 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,
|
// exceptions should have been handled earlier, but catch them here,
|
||||||
// just in case...
|
// just in case...
|
||||||
|
|||||||
@ -26,7 +26,6 @@
|
|||||||
|
|
||||||
(require 'mu4e-helpers)
|
(require 'mu4e-helpers)
|
||||||
|
|
||||||
|
|
||||||
;;; Configuration
|
;;; Configuration
|
||||||
(defcustom mu4e-mu-home nil
|
(defcustom mu4e-mu-home nil
|
||||||
"Location of an alternate mu home directory.
|
"Location of an alternate mu home directory.
|
||||||
@ -93,6 +92,42 @@ stop/start mu4e."
|
|||||||
:group 'mu4e
|
:group 'mu4e
|
||||||
:safe 'booleanp)
|
: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
|
;; Cached data
|
||||||
(defvar mu4e-maildir-list)
|
(defvar mu4e-maildir-list)
|
||||||
|
|
||||||
@ -467,6 +502,7 @@ As per issue #2198."
|
|||||||
`(,(when mu4e-mu-debug "--debug")
|
`(,(when mu4e-mu-debug "--debug")
|
||||||
"server"
|
"server"
|
||||||
,(when mu4e-mu-allow-temp-file "--allow-temp-file")
|
,(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)))))
|
,(when mu4e-mu-home (format "--muhome=%s" mu4e-mu-home)))))
|
||||||
|
|
||||||
(defun mu4e--version-check ()
|
(defun mu4e--version-check ()
|
||||||
@ -491,12 +527,15 @@ As per issue #2198."
|
|||||||
(mu4e-message "Found mu version %s" version)))))
|
(mu4e-message "Found mu version %s" version)))))
|
||||||
|
|
||||||
(defun mu4e-server-repl ()
|
(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.
|
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)
|
(interactive)
|
||||||
(if (mu4e-running-p)
|
(if (mu4e-running-p)
|
||||||
(mu4e-error "Cannot run repl when mu4e is running")
|
(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())};
|
auto mime_part{GMIME_PART(part.mime_object().object())};
|
||||||
SCM mime_part_scm{to_scm(mime_part)};
|
SCM mime_part_scm{to_scm(mime_part)};
|
||||||
SCM alist_scm{to_scm(idx, parts[idx])};
|
SCM alist_scm{to_scm(idx, parts[idx])};
|
||||||
|
SCM item{scm_cons(mime_part_scm, alist_scm)};
|
||||||
|
|
||||||
parts_scm = scm_append_x(
|
parts_scm = scm_cons(item, parts_scm);
|
||||||
scm_list_2(parts_scm,
|
|
||||||
scm_list_1(
|
|
||||||
scm_cons(mime_part_scm, alist_scm))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts_scm;
|
return scm_reverse_x(parts_scm, SCM_EOL);
|
||||||
|
|
||||||
|
|
||||||
} catch (const ScmError& err) {
|
} catch (const ScmError& err) {
|
||||||
err.throw_scm();
|
err.throw_scm();
|
||||||
|
|||||||
@ -21,7 +21,8 @@
|
|||||||
;; after printing UNIX-CONNECT:<socket-file>\n on stdout
|
;; after printing UNIX-CONNECT:<socket-file>\n on stdout
|
||||||
(let ((socket-path (getenv "MU_SCM_SOCKET_PATH")))
|
(let ((socket-path (getenv "MU_SCM_SOCKET_PATH")))
|
||||||
(when 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
|
(run-server
|
||||||
(make-unix-domain-server-socket #:path socket-path))))
|
(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)};
|
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) {
|
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
|
} else
|
||||||
return field_opt->id;
|
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));
|
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))
|
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 sort_field_id = to_sort_field_id(sort_field_scm, func, 5);
|
||||||
const auto reverse(from_scm<bool>(reverse_scm, func, 6));
|
const auto reverse(from_scm<bool>(reverse_scm, func, 6));
|
||||||
|
|||||||
239
scm/mu-scm.cc
239
scm/mu-scm.cc
@ -16,23 +16,31 @@
|
|||||||
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
**
|
**
|
||||||
*/
|
*/
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
#include "mu-scm.hh"
|
#include "mu-scm.hh"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "mu-utils.hh"
|
#include "mu-utils.hh"
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include "mu-scm-types.hh"
|
#include "mu-scm-types.hh"
|
||||||
|
|
||||||
|
#ifdef HAVE_PTHREAD_SETNAME_NP
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace Mu;
|
using namespace Mu;
|
||||||
using namespace Mu::Scm;
|
using namespace Mu::Scm;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
static const Mu::Scm::Config *config{};
|
SCM mu_mod; // The mu module
|
||||||
static SCM mu_mod; // The mu module
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,14 +65,45 @@ init_options(const Options& opts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
init_module_mu(void* _data)
|
init_misc()
|
||||||
{
|
{
|
||||||
init_options(config->options);
|
scm_define(make_symbol("level-critical"), to_scm(G_LOG_LEVEL_CRITICAL));
|
||||||
init_store(config->store);
|
scm_define(make_symbol("level-warning"), to_scm(G_LOG_LEVEL_WARNING));
|
||||||
init_message();
|
scm_define(make_symbol("level-info"), to_scm(G_LOG_LEVEL_INFO));
|
||||||
init_mime();
|
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>
|
static const Result<std::string>
|
||||||
make_mu_scm_path(const std::string& fname) {
|
make_mu_scm_path(const std::string& fname) {
|
||||||
|
|
||||||
@ -84,23 +123,20 @@ make_mu_scm_path(const std::string& fname) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
static std::string mu_scm_path;
|
std::string mu_scm_path;
|
||||||
static std::string mu_scm_repl_path;
|
std::string mu_scm_repl_path;
|
||||||
static std::string mu_scm_socket_path;
|
std::string mu_scm_socket_path;
|
||||||
constexpr auto SOCKET_PATH_ENV = "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>
|
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
|
// do a checks _before_ entering guile, so we get a bit more civilized
|
||||||
// error message.
|
// error message.
|
||||||
|
|
||||||
if (const auto path = make_mu_scm_path("mu-scm.scm"); path)
|
if (const auto path = make_mu_scm_path("mu-scm.scm"); path)
|
||||||
mu_scm_path = *path;
|
mu_scm_path = *path;
|
||||||
else
|
else
|
||||||
@ -111,8 +147,8 @@ prepare_run(const Mu::Scm::Config& conf)
|
|||||||
else
|
else
|
||||||
return Err(path.error());
|
return Err(path.error());
|
||||||
|
|
||||||
if (config->options.scm.script_path) {
|
if (opts.scm.script_path) {
|
||||||
const auto path{config->options.scm.script_path->c_str()};
|
const auto path{opts.scm.script_path->c_str()};
|
||||||
if (const auto res = ::access(path, R_OK); res != 0) {
|
if (const auto res = ::access(path, R_OK); res != 0) {
|
||||||
return Err(Error::Code::InvalidArgument,
|
return Err(Error::Code::InvalidArgument,
|
||||||
"cannot read '{}': {}", path, ::strerror(errno));
|
"cannot read '{}': {}", path, ::strerror(errno));
|
||||||
@ -122,73 +158,132 @@ prepare_run(const Mu::Scm::Config& conf)
|
|||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a unique unix-socket path
|
static void
|
||||||
static std::string
|
prepare_script(const Options& opts, StrVec& args)
|
||||||
maybe_set_uds_path(bool set)
|
|
||||||
{
|
{
|
||||||
if (set) {
|
static std::string cmd; // keep alive
|
||||||
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 {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Result<void>
|
|
||||||
Mu::Scm::run(const Mu::Scm::Config& conf)
|
|
||||||
{
|
|
||||||
if (const auto res = prepare_run(conf); !res)
|
|
||||||
return Err(res.error());
|
|
||||||
|
|
||||||
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
|
// XXX: couldn't get another combination of -l/-s/-e/-c to work
|
||||||
// a) invokes `main' with arguments, and
|
// a) invokes `main' with arguments, and
|
||||||
// b) exits (rather than drop to a shell)
|
// b) exits (rather than drop to a shell)
|
||||||
// but, what works is to manually specify (main ....)
|
// but, what works is to manually specify (main ....)
|
||||||
cmd = "(main " + quote(*opts.script_path);
|
cmd = "(main " + quote(*opts.scm.script_path);
|
||||||
for (const auto& scriptarg : opts.params)
|
for (const auto& scriptarg : opts.scm.params)
|
||||||
cmd += " " + quote(scriptarg);
|
cmd += " " + quote(scriptarg);
|
||||||
cmd += ")";
|
cmd += ")";
|
||||||
for (const auto& arg: {
|
|
||||||
"-l", opts.script_path->c_str(),
|
args.emplace_back("-l");
|
||||||
"-c", cmd.c_str()})
|
args.emplace_back(*opts.scm.script_path);
|
||||||
args.emplace_back(arg);
|
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 {
|
} else {
|
||||||
// otherwise, drop us into an interactive shell/repl
|
mu_debug("unlinked {}", sock);
|
||||||
// or start listening on a domain socket.
|
}
|
||||||
mu_scm_socket_path =
|
}
|
||||||
maybe_set_uds_path(config->options.scm.listen);
|
|
||||||
|
static void
|
||||||
|
prepare_shell(const Options& opts, StrVec& args)
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
|
||||||
args.emplace_back("--no-auto-compile");
|
args.emplace_back("--no-auto-compile");
|
||||||
args.emplace_back("-l");
|
args.emplace_back("-l");
|
||||||
args.emplace_back(mu_scm_repl_path.c_str());
|
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...*/
|
/* ahem...*/
|
||||||
scm_shell(std::size(args), const_cast<char**>(args.data()));
|
return const_cast<char*>(strarg.c_str());
|
||||||
|
});
|
||||||
|
scm_shell(args.size(), args.data());
|
||||||
|
|
||||||
}, {}); // never returns.
|
}, {}); // 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();
|
return Ok();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef BUILD_TESTS
|
#ifdef BUILD_TESTS
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -199,7 +294,6 @@ Mu::Scm::run(const Mu::Scm::Config& conf)
|
|||||||
#include <mu-store.hh>
|
#include <mu-store.hh>
|
||||||
#include "utils/mu-test-utils.hh"
|
#include "utils/mu-test-utils.hh"
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_scm_script()
|
test_scm_script()
|
||||||
{
|
{
|
||||||
@ -233,13 +327,8 @@ test_scm_script()
|
|||||||
Mu::Options opts{};
|
Mu::Options opts{};
|
||||||
opts.scm.script_path = join_paths(MU_SCM_SRCDIR, "mu-scm-test.scm");
|
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);
|
assert_valid_result(res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,28 +39,24 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
namespace Mu::Scm {
|
namespace Mu::Scm {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration object
|
* Start a guile REPL or program
|
||||||
*
|
*
|
||||||
*/
|
* Initialize the Scm sub-system, then start a REPL or run a script,
|
||||||
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,
|
|
||||||
* based on the configuration.
|
* 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
|
* @return Ok() or some error
|
||||||
*/
|
*/
|
||||||
Result<void> run(const Config& conf);
|
Result<void> run(const Store& store, const Options& opts,
|
||||||
|
bool blocking=true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helpers
|
* Helpers
|
||||||
@ -223,15 +219,18 @@ namespace Mu::Scm {
|
|||||||
return scm_from_utf8_string(val);
|
return scm_from_utf8_string(val);
|
||||||
else if constexpr (std::is_same_v<Type, std::vector<std::string>>) {
|
else if constexpr (std::is_same_v<Type, std::vector<std::string>>) {
|
||||||
SCM lst{SCM_EOL};
|
SCM lst{SCM_EOL};
|
||||||
for (const auto& s: val)
|
for (auto it = val.end(); it-- != val.begin();)
|
||||||
lst = scm_append_x(scm_list_2(lst,
|
lst = scm_cons(to_scm(*it), lst);
|
||||||
scm_list_1(to_scm(s))));
|
|
||||||
return lst;
|
return lst;
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<Type, bool>)
|
else if constexpr (std::is_same_v<Type, bool>)
|
||||||
return scm_from_bool(val);
|
return scm_from_bool(val);
|
||||||
else if constexpr (std::is_same_v<Type, size_t>)
|
else if constexpr (std::is_same_v<Type, size_t>)
|
||||||
return scm_from_size_t(val);
|
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>)
|
else if constexpr (std::is_same_v<Type, int64_t>)
|
||||||
return scm_from_int64(val);
|
return scm_from_int64(val);
|
||||||
else if constexpr (std::is_same_v<Type, uint64_t>)
|
else if constexpr (std::is_same_v<Type, uint64_t>)
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
:use-module (system foreign)
|
:use-module (system foreign)
|
||||||
:use-module (rnrs bytevectors)
|
:use-module (rnrs bytevectors)
|
||||||
:use-module (ice-9 optargs)
|
:use-module (ice-9 optargs)
|
||||||
|
:use-module (ice-9 format)
|
||||||
:use-module (ice-9 binary-ports)
|
:use-module (ice-9 binary-ports)
|
||||||
#:export (
|
#:export (
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@ -100,11 +101,16 @@
|
|||||||
%options
|
%options
|
||||||
;; %preferences
|
;; %preferences
|
||||||
|
|
||||||
|
;; logging
|
||||||
|
debug
|
||||||
|
info
|
||||||
|
warning
|
||||||
|
critical
|
||||||
|
|
||||||
;; helpers
|
;; helpers
|
||||||
string->time
|
string->time
|
||||||
time->string))
|
time->string))
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; Helpers
|
;; Helpers
|
||||||
@ -637,3 +643,43 @@ UTC time.
|
|||||||
(let ((t (if utc? (gmtime time-t) (localtime time-t))))
|
(let ((t (if utc? (gmtime time-t) (localtime time-t))))
|
||||||
(strftime format t))
|
(strftime format t))
|
||||||
#f)))
|
#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::
|
* Starting the REPL::
|
||||||
* Listening on a Unix Domain Socket::
|
* Listening on a Unix Domain Socket::
|
||||||
* Hooking up with GNU/Emacs and Geiser::
|
* Hooking up with GNU/Emacs and Geiser::
|
||||||
|
* Hooking up with Mu4e::
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
This chapter walks you through the installation and basic setup.
|
This chapter walks you through the installation and basic setup.
|
||||||
@ -184,18 +185,18 @@ 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:
|
(randomized) UNIX domain socket name and blocks after that; for instance:
|
||||||
@example
|
@example
|
||||||
$ mu scm --listen
|
$ mu scm --listen
|
||||||
UNIX-CONNECT:/run/user/1000/mu-scm-socket-6ef6222e
|
/run/user/1000/mu-scm-15269.sock
|
||||||
@end example
|
@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
|
@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
|
GNU Guile 3.0.9
|
||||||
Copyright (C) 1995-2023 Free Software Foundation, Inc.
|
Copyright (C) 1995-2023 Free Software Foundation, Inc.
|
||||||
|
|
||||||
Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
|
Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This
|
||||||
This program is free software, and you are welcome to redistribute it
|
program is free software, and you are welcome to redistribute it under certain
|
||||||
under certain conditions; type `,show c' for details.
|
conditions; type `,show c' for details.
|
||||||
|
|
||||||
Enter `,help' for help.
|
Enter `,help' for help.
|
||||||
scheme@@(guile-user)>
|
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
|
Assuming you have installed the @t{guile-geiser} package, the following snippet
|
||||||
makes that easy:
|
makes that easy:
|
||||||
@lisp
|
@lisp
|
||||||
(require 'guiser-guile)
|
(require 'geiser-guile)
|
||||||
|
|
||||||
(defvar mu-scm-listen-command "mu scm --listen"
|
(defvar mu-scm-listen-command "mu scm --listen"
|
||||||
"mu command to start an scm repl listening on a socket.")
|
"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*"
|
:name "*mu-scm-repl*"
|
||||||
:command `("sh" "-c" ,mu-scm-listen-command)
|
:command `("sh" "-c" ,mu-scm-listen-command)
|
||||||
:filter (lambda (_proc chunk)
|
: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))))))
|
(geiser-connect-local 'guile (match-string 1 chunk))))))
|
||||||
@end lisp
|
@end lisp
|
||||||
|
|
||||||
After evaluating this, you can use @command{M-x mu-scm-geiser-connect} to start
|
After evaluating this, you can use @command{M-x mu-scm-geiser-connect} to start
|
||||||
the REPL, with all the Geiser bells & whistles.
|
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
|
@node Shell
|
||||||
@chapter Shell
|
@chapter Shell
|
||||||
|
|
||||||
@ -919,6 +939,26 @@ their default values.
|
|||||||
@node Helpers
|
@node Helpers
|
||||||
@section 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?)]
|
@deffn {Scheme Procedure} string->time timestr [#:utc? (assoc-ref %preferences 'utc?)]
|
||||||
@end deffn
|
@end deffn
|
||||||
Convert some ISO-8601-style time-string to a seconds-since-epoch @t{time_t}
|
Convert some ISO-8601-style time-string to a seconds-since-epoch @t{time_t}
|
||||||
|
|||||||
Reference in New Issue
Block a user