Merge branch 'wip/sexp'
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -121,3 +121,4 @@ perf.data.old
|
|||||||
mu-*-coverage
|
mu-*-coverage
|
||||||
mu*tar.xz
|
mu*tar.xz
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
/lib/utils/test-sexp
|
||||||
|
|||||||
5
NEWS.org
5
NEWS.org
@ -1,5 +1,5 @@
|
|||||||
#+STARTUP:showall
|
#+STARTUP:showall
|
||||||
* NEWS (user visible changes)
|
* NEWS (user visible changes & bigger non-visible ones)
|
||||||
|
|
||||||
* 1.5.x (unreleased, development version)
|
* 1.5.x (unreleased, development version)
|
||||||
|
|
||||||
@ -14,6 +14,9 @@
|
|||||||
|
|
||||||
- Optionally provide readline support for the mu server (when in tty-mode)
|
- Optionally provide readline support for the mu server (when in tty-mode)
|
||||||
|
|
||||||
|
- Reworked the way mu generates s-expressions for mu4e; they are created
|
||||||
|
programmatically now instead of through string building.
|
||||||
|
|
||||||
*** mu4e
|
*** mu4e
|
||||||
|
|
||||||
- Include maildir-shortcuts in the main-view with overall/unread counts,
|
- Include maildir-shortcuts in the main-view with overall/unread counts,
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
## Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
AC_PREREQ([2.68])
|
AC_PREREQ([2.68])
|
||||||
AC_INIT([mu],[1.5.1],[https://github.com/djcb/mu/issues],[mu])
|
AC_INIT([mu],[1.5.2],[https://github.com/djcb/mu/issues],[mu])
|
||||||
AC_COPYRIGHT([Copyright (C) 2008-2020 Dirk-Jan C. Binnema])
|
AC_COPYRIGHT([Copyright (C) 2008-2020 Dirk-Jan C. Binnema])
|
||||||
AC_CONFIG_HEADERS([config.h])
|
AC_CONFIG_HEADERS([config.h])
|
||||||
AC_CONFIG_SRCDIR([mu/mu.cc])
|
AC_CONFIG_SRCDIR([mu/mu.cc])
|
||||||
|
|||||||
@ -100,7 +100,7 @@ libmu_la_SOURCES= \
|
|||||||
mu-msg-prio.c \
|
mu-msg-prio.c \
|
||||||
mu-msg-prio.h \
|
mu-msg-prio.h \
|
||||||
mu-msg-priv.h \
|
mu-msg-priv.h \
|
||||||
mu-msg-sexp.c \
|
mu-msg-sexp.cc \
|
||||||
mu-msg.c \
|
mu-msg.c \
|
||||||
mu-msg.h \
|
mu-msg.h \
|
||||||
mu-msg.h \
|
mu-msg.h \
|
||||||
|
|||||||
@ -1,634 +0,0 @@
|
|||||||
/*
|
|
||||||
** Copyright (C) 2011-2017 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
|
|
||||||
** Free Software Foundation; either version 3, or (at your option) any
|
|
||||||
** later version.
|
|
||||||
**
|
|
||||||
** This program is distributed in the hope that it will be useful,
|
|
||||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
** GNU General Public License for more details.
|
|
||||||
**
|
|
||||||
** You should have received a copy of the GNU General Public License
|
|
||||||
** along with this program; if not, write to the Free Software Foundation,
|
|
||||||
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
**
|
|
||||||
*/
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
#include "utils/mu-str.h"
|
|
||||||
#include "mu-msg.h"
|
|
||||||
#include "mu-msg-iter.h"
|
|
||||||
#include "mu-msg-part.h"
|
|
||||||
#include "mu-maildir.h"
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_attr_list (GString *gstr, const char* elm, const GSList *lst)
|
|
||||||
{
|
|
||||||
const GSList *cur;
|
|
||||||
|
|
||||||
if (!lst)
|
|
||||||
return; /* empty list, don't include */
|
|
||||||
|
|
||||||
g_string_append_printf (gstr, "\t:%s ( ", elm);
|
|
||||||
|
|
||||||
for (cur = lst; cur; cur = g_slist_next(cur)) {
|
|
||||||
char *str;
|
|
||||||
str = mu_str_escape_c_literal
|
|
||||||
((const gchar*)cur->data, TRUE);
|
|
||||||
g_string_append_printf (gstr, "%s ", str);
|
|
||||||
g_free (str);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_string_append (gstr, ")\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_attr (GString *gstr, const char* elm, const char *str)
|
|
||||||
{
|
|
||||||
gchar *esc, *utf8, *cur;
|
|
||||||
|
|
||||||
if (!str || strlen(str) == 0)
|
|
||||||
return; /* empty: don't include */
|
|
||||||
|
|
||||||
|
|
||||||
utf8 = mu_str_utf8ify (str);
|
|
||||||
|
|
||||||
for (cur = utf8; *cur; ++cur)
|
|
||||||
if (iscntrl(*cur))
|
|
||||||
*cur = ' ';
|
|
||||||
|
|
||||||
esc = mu_str_escape_c_literal (utf8, TRUE);
|
|
||||||
g_free (utf8);
|
|
||||||
|
|
||||||
g_string_append_printf (gstr, "\t:%s %s\n", elm, esc);
|
|
||||||
g_free (esc);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_body_attr (GString *gstr, const char* elm, const char *str)
|
|
||||||
{
|
|
||||||
gchar *esc;
|
|
||||||
|
|
||||||
if (!str || strlen(str) == 0)
|
|
||||||
return; /* empty: don't include */
|
|
||||||
|
|
||||||
esc = mu_str_escape_c_literal (str, TRUE);
|
|
||||||
|
|
||||||
g_string_append_printf (gstr, "\t:%s %s\n", elm, esc);
|
|
||||||
g_free (esc);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct _ContactData {
|
|
||||||
gboolean from, to, cc, bcc, reply_to;
|
|
||||||
GString *gstr;
|
|
||||||
MuMsgContactType prev_ctype;
|
|
||||||
};
|
|
||||||
typedef struct _ContactData ContactData;
|
|
||||||
|
|
||||||
static gchar*
|
|
||||||
get_name_email_pair (MuMsgContact *c)
|
|
||||||
{
|
|
||||||
gchar *name, *email, *pair;
|
|
||||||
|
|
||||||
name = (char*)mu_msg_contact_name(c);
|
|
||||||
email = (char*)mu_msg_contact_email(c);
|
|
||||||
|
|
||||||
name = name ? mu_str_escape_c_literal (name, TRUE) : NULL;
|
|
||||||
email = email ? mu_str_escape_c_literal (email, TRUE) : NULL;
|
|
||||||
|
|
||||||
pair = g_strdup_printf ("(%s . %s)",
|
|
||||||
name ? name : "nil",
|
|
||||||
email ? email : "nil");
|
|
||||||
g_free (name);
|
|
||||||
g_free (email);
|
|
||||||
|
|
||||||
return pair;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
add_prefix_maybe (GString *gstr, gboolean *field, const char *prefix)
|
|
||||||
{
|
|
||||||
/* if there's nothing in the field yet, add the prefix */
|
|
||||||
if (!*field)
|
|
||||||
g_string_append (gstr, prefix);
|
|
||||||
|
|
||||||
*field = TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
each_contact (MuMsgContact *c, ContactData *cdata)
|
|
||||||
{
|
|
||||||
char *pair;
|
|
||||||
MuMsgContactType ctype;
|
|
||||||
|
|
||||||
ctype = mu_msg_contact_type (c);
|
|
||||||
|
|
||||||
/* if the current type is not the previous type, close the
|
|
||||||
* previous first */
|
|
||||||
if (cdata->prev_ctype != ctype && cdata->prev_ctype != (unsigned)-1)
|
|
||||||
g_string_append (cdata->gstr, ")\n");
|
|
||||||
|
|
||||||
switch (ctype) {
|
|
||||||
|
|
||||||
case MU_MSG_CONTACT_TYPE_FROM:
|
|
||||||
add_prefix_maybe (cdata->gstr, &cdata->from, "\t:from (");
|
|
||||||
break;
|
|
||||||
case MU_MSG_CONTACT_TYPE_TO:
|
|
||||||
add_prefix_maybe (cdata->gstr, &cdata->to, "\t:to (");
|
|
||||||
break;
|
|
||||||
case MU_MSG_CONTACT_TYPE_CC:
|
|
||||||
add_prefix_maybe (cdata->gstr, &cdata->cc, "\t:cc (");
|
|
||||||
break;
|
|
||||||
case MU_MSG_CONTACT_TYPE_BCC:
|
|
||||||
add_prefix_maybe (cdata->gstr, &cdata->bcc, "\t:bcc (");
|
|
||||||
break;
|
|
||||||
case MU_MSG_CONTACT_TYPE_REPLY_TO:
|
|
||||||
add_prefix_maybe (cdata->gstr, &cdata->reply_to,
|
|
||||||
"\t:reply-to (");
|
|
||||||
break;
|
|
||||||
default: g_return_val_if_reached (FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
cdata->prev_ctype = ctype;
|
|
||||||
|
|
||||||
pair = get_name_email_pair (c);
|
|
||||||
g_string_append (cdata->gstr, pair);
|
|
||||||
g_free (pair);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
maybe_append_list_post (GString *gstr, MuMsg *msg)
|
|
||||||
{
|
|
||||||
/* some mailing lists do not set the reply-to; see pull #1278. So for
|
|
||||||
* those cases, check the List-Post address and use that instead */
|
|
||||||
|
|
||||||
GMatchInfo *minfo;
|
|
||||||
GRegex *rx;
|
|
||||||
const char* list_post;
|
|
||||||
|
|
||||||
list_post = mu_msg_get_header (msg, "List-Post");
|
|
||||||
if (!list_post)
|
|
||||||
return;
|
|
||||||
|
|
||||||
rx = g_regex_new ("<?mailto:([a-z0-9%+@._-]+)>?", G_REGEX_CASELESS, 0, NULL);
|
|
||||||
g_return_if_fail(rx);
|
|
||||||
|
|
||||||
if (g_regex_match (rx, list_post, 0, &minfo)) {
|
|
||||||
char *addr;
|
|
||||||
addr = g_match_info_fetch (minfo, 1);
|
|
||||||
g_string_append_printf (gstr,"\t:list-post ((nil . \"%s\"))\n", addr);
|
|
||||||
g_free(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_match_info_free (minfo);
|
|
||||||
g_regex_unref (rx);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_contacts (GString *gstr, MuMsg *msg)
|
|
||||||
{
|
|
||||||
ContactData cdata;
|
|
||||||
|
|
||||||
cdata.from = cdata.to = cdata.cc = cdata.bcc
|
|
||||||
= cdata.reply_to = FALSE;
|
|
||||||
cdata.gstr = gstr;
|
|
||||||
cdata.prev_ctype = (unsigned)-1;
|
|
||||||
|
|
||||||
mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact,
|
|
||||||
&cdata);
|
|
||||||
if (cdata.from || cdata.to || cdata.cc || cdata.bcc || cdata.reply_to)
|
|
||||||
gstr = g_string_append (gstr, ")\n");
|
|
||||||
|
|
||||||
maybe_append_list_post (gstr, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct _FlagData {
|
|
||||||
char *flagstr;
|
|
||||||
MuFlags msgflags;
|
|
||||||
};
|
|
||||||
typedef struct _FlagData FlagData;
|
|
||||||
|
|
||||||
static void
|
|
||||||
each_flag (MuFlags flag, FlagData *fdata)
|
|
||||||
{
|
|
||||||
if (!(flag & fdata->msgflags))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!fdata->flagstr)
|
|
||||||
fdata->flagstr = g_strdup (mu_flag_name(flag));
|
|
||||||
else {
|
|
||||||
gchar *tmp;
|
|
||||||
tmp = g_strconcat (fdata->flagstr, " ",
|
|
||||||
mu_flag_name(flag), NULL);
|
|
||||||
g_free (fdata->flagstr);
|
|
||||||
fdata->flagstr = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_flags (GString *gstr, MuMsg *msg)
|
|
||||||
{
|
|
||||||
FlagData fdata;
|
|
||||||
|
|
||||||
fdata.msgflags = mu_msg_get_flags (msg);
|
|
||||||
fdata.flagstr = NULL;
|
|
||||||
|
|
||||||
mu_flags_foreach ((MuFlagsForeachFunc)each_flag, &fdata);
|
|
||||||
if (fdata.flagstr)
|
|
||||||
g_string_append_printf (gstr, "\t:flags (%s)\n",
|
|
||||||
fdata.flagstr);
|
|
||||||
g_free (fdata.flagstr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static char*
|
|
||||||
get_temp_file (MuMsg *msg, MuMsgOptions opts, unsigned index)
|
|
||||||
{
|
|
||||||
char *path;
|
|
||||||
GError *err;
|
|
||||||
|
|
||||||
err = NULL;
|
|
||||||
path = mu_msg_part_get_cache_path (msg, opts, index, &err);
|
|
||||||
if (!path)
|
|
||||||
goto errexit;
|
|
||||||
|
|
||||||
if (!mu_msg_part_save (msg, opts, path, index, &err))
|
|
||||||
goto errexit;
|
|
||||||
|
|
||||||
return path;
|
|
||||||
|
|
||||||
errexit:
|
|
||||||
g_warning ("failed to save mime part: %s",
|
|
||||||
err->message ? err->message : "something went wrong");
|
|
||||||
g_clear_error (&err);
|
|
||||||
g_free (path);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static gchar*
|
|
||||||
get_temp_file_maybe (MuMsg *msg, MuMsgPart *part, MuMsgOptions opts)
|
|
||||||
{
|
|
||||||
char *tmp, *tmpfile;
|
|
||||||
|
|
||||||
opts |= MU_MSG_OPTION_USE_EXISTING;
|
|
||||||
|
|
||||||
if (!(opts & MU_MSG_OPTION_EXTRACT_IMAGES) ||
|
|
||||||
g_ascii_strcasecmp (part->type, "image") != 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
tmp = get_temp_file (msg, opts, part->index);
|
|
||||||
if (!tmp)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
tmpfile = mu_str_escape_c_literal (tmp, TRUE);
|
|
||||||
g_free (tmp);
|
|
||||||
return tmpfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct _PartInfo {
|
|
||||||
char *parts;
|
|
||||||
MuMsgOptions opts;
|
|
||||||
};
|
|
||||||
typedef struct _PartInfo PartInfo;
|
|
||||||
|
|
||||||
static char*
|
|
||||||
sig_verdict (MuMsgPart *mpart)
|
|
||||||
{
|
|
||||||
char *signers, *s;
|
|
||||||
const char *verdict;
|
|
||||||
MuMsgPartSigStatusReport *report;
|
|
||||||
|
|
||||||
report = mpart->sig_status_report;
|
|
||||||
if (!report)
|
|
||||||
return g_strdup ("");
|
|
||||||
|
|
||||||
switch (report->verdict) {
|
|
||||||
case MU_MSG_PART_SIG_STATUS_GOOD:
|
|
||||||
verdict = ":signature verified";
|
|
||||||
break;
|
|
||||||
case MU_MSG_PART_SIG_STATUS_BAD:
|
|
||||||
verdict = ":signature bad";
|
|
||||||
break;
|
|
||||||
case MU_MSG_PART_SIG_STATUS_ERROR:
|
|
||||||
verdict = ":signature unverified";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
verdict = "";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!report->signers)
|
|
||||||
return g_strdup (verdict);
|
|
||||||
|
|
||||||
signers = mu_str_escape_c_literal (report->signers, TRUE);
|
|
||||||
s = g_strdup_printf ("%s :signers %s", verdict, signers);
|
|
||||||
g_free (signers);
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char*
|
|
||||||
dec_verdict (MuMsgPart *mpart)
|
|
||||||
{
|
|
||||||
MuMsgPartType ptype;
|
|
||||||
|
|
||||||
ptype = mpart->part_type;
|
|
||||||
|
|
||||||
if (ptype & MU_MSG_PART_TYPE_DECRYPTED)
|
|
||||||
return ":decryption succeeded";
|
|
||||||
else if (ptype & MU_MSG_PART_TYPE_ENCRYPTED)
|
|
||||||
return ":decryption failed";
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static gchar *
|
|
||||||
get_part_type_string (MuMsgPartType ptype)
|
|
||||||
{
|
|
||||||
GString *gstr;
|
|
||||||
unsigned u;
|
|
||||||
struct PartTypes {
|
|
||||||
MuMsgPartType ptype;
|
|
||||||
const char* name;
|
|
||||||
} ptypes[] = {
|
|
||||||
{ MU_MSG_PART_TYPE_LEAF, "leaf" },
|
|
||||||
{ MU_MSG_PART_TYPE_MESSAGE, "message" },
|
|
||||||
{ MU_MSG_PART_TYPE_INLINE, "inline" },
|
|
||||||
{ MU_MSG_PART_TYPE_ATTACHMENT, "attachment" },
|
|
||||||
{ MU_MSG_PART_TYPE_SIGNED, "signed" },
|
|
||||||
{ MU_MSG_PART_TYPE_ENCRYPTED, "encrypted" }
|
|
||||||
};
|
|
||||||
|
|
||||||
gstr = g_string_sized_new (100); /* more than enough */
|
|
||||||
gstr = g_string_append_c (gstr, '(');
|
|
||||||
|
|
||||||
for (u = 0; u!= G_N_ELEMENTS(ptypes); ++u) {
|
|
||||||
if (ptype & ptypes[u].ptype) {
|
|
||||||
if (gstr->len > 1)
|
|
||||||
gstr = g_string_append_c (gstr, ' ');
|
|
||||||
gstr = g_string_append (gstr, ptypes[u].name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gstr = g_string_append_c (gstr, ')');
|
|
||||||
|
|
||||||
return g_string_free (gstr, FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
each_part (MuMsg *msg, MuMsgPart *part, PartInfo *pinfo)
|
|
||||||
{
|
|
||||||
char *name, *encname, *tmp, *parttype;
|
|
||||||
char *tmpfile, *cidesc, *verdict;
|
|
||||||
const char *cid;
|
|
||||||
|
|
||||||
name = mu_msg_part_get_filename (part, TRUE);
|
|
||||||
encname = name ?
|
|
||||||
mu_str_escape_c_literal(name, TRUE) :
|
|
||||||
g_strdup("\"noname\"");
|
|
||||||
g_free (name);
|
|
||||||
|
|
||||||
tmpfile = get_temp_file_maybe (msg, part, pinfo->opts);
|
|
||||||
parttype = get_part_type_string (part->part_type);
|
|
||||||
verdict = sig_verdict (part);
|
|
||||||
|
|
||||||
cid = mu_msg_part_get_content_id(part);
|
|
||||||
cidesc = cid ? mu_str_escape_c_literal(cid, TRUE) : NULL;
|
|
||||||
|
|
||||||
tmp = g_strdup_printf
|
|
||||||
("%s(:index %d :name %s :mime-type \"%s/%s\"%s%s "
|
|
||||||
":type %s "
|
|
||||||
":attachment %s %s%s :size %i %s %s)",
|
|
||||||
pinfo->parts ? pinfo->parts: "",
|
|
||||||
part->index,
|
|
||||||
encname,
|
|
||||||
part->type ? part->type : "application",
|
|
||||||
part->subtype ? part->subtype : "octet-stream",
|
|
||||||
tmpfile ? " :temp" : "", tmpfile ? tmpfile : "",
|
|
||||||
parttype,
|
|
||||||
mu_msg_part_maybe_attachment (part) ? "t" : "nil",
|
|
||||||
cidesc ? " :cid" : "", cidesc ? cidesc : "",
|
|
||||||
(int)part->size,
|
|
||||||
verdict,
|
|
||||||
dec_verdict (part));
|
|
||||||
|
|
||||||
g_free (encname);
|
|
||||||
g_free (tmpfile);
|
|
||||||
g_free (parttype);
|
|
||||||
g_free (verdict);
|
|
||||||
g_free (cidesc);
|
|
||||||
|
|
||||||
g_free (pinfo->parts);
|
|
||||||
pinfo->parts = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_parts (GString *gstr, MuMsg *msg, MuMsgOptions opts)
|
|
||||||
{
|
|
||||||
PartInfo pinfo;
|
|
||||||
|
|
||||||
pinfo.parts = NULL;
|
|
||||||
pinfo.opts = opts;
|
|
||||||
|
|
||||||
if (!mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)each_part,
|
|
||||||
&pinfo)) {
|
|
||||||
/* do nothing */
|
|
||||||
} else if (pinfo.parts) {
|
|
||||||
g_string_append_printf (gstr, "\t:parts (%s)\n", pinfo.parts);
|
|
||||||
g_free (pinfo.parts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_thread_info (GString *gstr, const MuMsgIterThreadInfo *ti)
|
|
||||||
{
|
|
||||||
g_string_append_printf
|
|
||||||
(gstr, "\t:thread (:path \"%s\" :level %u%s%s%s%s%s)\n",
|
|
||||||
ti->threadpath,
|
|
||||||
ti->level,
|
|
||||||
ti->prop & MU_MSG_ITER_THREAD_PROP_FIRST_CHILD ?
|
|
||||||
" :first-child t" : "",
|
|
||||||
ti->prop & MU_MSG_ITER_THREAD_PROP_LAST_CHILD ?
|
|
||||||
" :last-child t" : "",
|
|
||||||
ti->prop & MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT ?
|
|
||||||
" :empty-parent t" : "",
|
|
||||||
ti->prop & MU_MSG_ITER_THREAD_PROP_DUP ?
|
|
||||||
" :duplicate t" : "",
|
|
||||||
ti->prop & MU_MSG_ITER_THREAD_PROP_HAS_CHILD ?
|
|
||||||
" :has-child t" : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_param (GString *gstr, const GSList *param)
|
|
||||||
{
|
|
||||||
for (;param; param = g_slist_next (param)) {
|
|
||||||
const char *str;
|
|
||||||
char *key, *value;
|
|
||||||
|
|
||||||
str = param->data;
|
|
||||||
key = str ? mu_str_escape_c_literal (str, FALSE) : g_strdup ("");
|
|
||||||
|
|
||||||
param = g_slist_next (param);
|
|
||||||
str = param->data;
|
|
||||||
value = str ? mu_str_escape_c_literal (str, FALSE) : g_strdup ("");
|
|
||||||
|
|
||||||
g_string_append_printf (gstr, "(\"%s\" . \"%s\")", key, value);
|
|
||||||
g_free (key);
|
|
||||||
g_free (value);
|
|
||||||
|
|
||||||
if (param->next)
|
|
||||||
g_string_append_c (gstr, ' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_message_file_parts (GString *gstr, MuMsg *msg, MuMsgOptions opts)
|
|
||||||
{
|
|
||||||
const char *str;
|
|
||||||
GError *err;
|
|
||||||
const GSList *params;
|
|
||||||
|
|
||||||
err = NULL;
|
|
||||||
|
|
||||||
if (!mu_msg_load_msg_file (msg, &err)) {
|
|
||||||
g_warning ("failed to load message file: %s",
|
|
||||||
err ? err->message : "some error occurred");
|
|
||||||
g_clear_error (&err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
append_sexp_parts (gstr, msg, opts);
|
|
||||||
append_sexp_contacts (gstr, msg);
|
|
||||||
|
|
||||||
/* add the user-agent / x-mailer */
|
|
||||||
str = mu_msg_get_header (msg, "User-Agent");
|
|
||||||
if (str || (str = mu_msg_get_header (msg, "X-Mailer")))
|
|
||||||
append_sexp_attr (gstr, "user-agent", str);
|
|
||||||
|
|
||||||
params = mu_msg_get_body_text_content_type_parameters (msg, opts);
|
|
||||||
if (params) {
|
|
||||||
g_string_append_printf (gstr, "\t:body-txt-params (");
|
|
||||||
append_sexp_param (gstr, params);
|
|
||||||
g_string_append_printf (gstr, ")\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
append_sexp_body_attr (gstr, "body-txt",
|
|
||||||
mu_msg_get_body_text(msg, opts));
|
|
||||||
append_sexp_body_attr (gstr, "body-html",
|
|
||||||
mu_msg_get_body_html(msg, opts));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_date_and_size (GString *gstr, MuMsg *msg)
|
|
||||||
{
|
|
||||||
time_t t;
|
|
||||||
size_t s;
|
|
||||||
|
|
||||||
t = mu_msg_get_date (msg);
|
|
||||||
if (t == (time_t)-1) /* invalid date? */
|
|
||||||
t = 0;
|
|
||||||
|
|
||||||
s = mu_msg_get_size (msg);
|
|
||||||
if (s == (size_t)-1) /* invalid size? */
|
|
||||||
s = 0;
|
|
||||||
|
|
||||||
g_string_append_printf
|
|
||||||
(gstr,
|
|
||||||
"\t:date (%d %u 0)\n\t:size %u\n",
|
|
||||||
(unsigned)(t >> 16),
|
|
||||||
(unsigned)(t & 0xffff),
|
|
||||||
(unsigned)s);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
append_sexp_tags (GString *gstr, MuMsg *msg)
|
|
||||||
{
|
|
||||||
const GSList *tags, *t;
|
|
||||||
gchar *tagesc;
|
|
||||||
GString *tagstr = g_string_new("");
|
|
||||||
|
|
||||||
tags = mu_msg_get_tags (msg);
|
|
||||||
|
|
||||||
for(t = tags; t; t = t->next) {
|
|
||||||
if (t != tags)
|
|
||||||
g_string_append(tagstr, " ");
|
|
||||||
|
|
||||||
tagesc = mu_str_escape_c_literal((const gchar *)t->data, TRUE);
|
|
||||||
g_string_append(tagstr, tagesc);
|
|
||||||
|
|
||||||
g_free(tagesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagstr->len > 0)
|
|
||||||
g_string_append_printf (gstr, "\t:tags (%s)\n",
|
|
||||||
tagstr->str);
|
|
||||||
g_string_free (tagstr, TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
char*
|
|
||||||
mu_msg_to_sexp (MuMsg *msg, unsigned docid, const MuMsgIterThreadInfo *ti,
|
|
||||||
MuMsgOptions opts)
|
|
||||||
{
|
|
||||||
GString *gstr;
|
|
||||||
|
|
||||||
g_return_val_if_fail (msg, NULL);
|
|
||||||
g_return_val_if_fail (!((opts & MU_MSG_OPTION_HEADERS_ONLY) &&
|
|
||||||
(opts & MU_MSG_OPTION_EXTRACT_IMAGES)),NULL);
|
|
||||||
gstr = g_string_sized_new
|
|
||||||
((opts & MU_MSG_OPTION_HEADERS_ONLY) ? 1024 : 8192);
|
|
||||||
|
|
||||||
if (docid == 0)
|
|
||||||
g_string_append (gstr, "(\n");
|
|
||||||
else
|
|
||||||
g_string_append_printf (gstr, "(\n\t:docid %u\n", docid);
|
|
||||||
|
|
||||||
if (ti)
|
|
||||||
append_sexp_thread_info (gstr, ti);
|
|
||||||
|
|
||||||
append_sexp_attr (gstr, "subject", mu_msg_get_subject (msg));
|
|
||||||
|
|
||||||
/* in the no-headers-only case (see below) we get a more complete list
|
|
||||||
* of contacts, so no need to get them here if that's the case */
|
|
||||||
if (opts & MU_MSG_OPTION_HEADERS_ONLY)
|
|
||||||
append_sexp_contacts (gstr, msg);
|
|
||||||
|
|
||||||
append_sexp_date_and_size (gstr, msg);
|
|
||||||
|
|
||||||
append_sexp_attr (gstr, "message-id", mu_msg_get_msgid (msg));
|
|
||||||
append_sexp_attr (gstr, "mailing-list",
|
|
||||||
mu_msg_get_mailing_list (msg));
|
|
||||||
append_sexp_attr (gstr, "path", mu_msg_get_path (msg));
|
|
||||||
append_sexp_attr (gstr, "maildir", mu_msg_get_maildir (msg));
|
|
||||||
g_string_append_printf (gstr, "\t:priority %s\n",
|
|
||||||
mu_msg_prio_name(mu_msg_get_prio(msg)));
|
|
||||||
append_sexp_flags (gstr, msg);
|
|
||||||
append_sexp_tags (gstr, msg);
|
|
||||||
|
|
||||||
append_sexp_attr_list (gstr, "references",
|
|
||||||
mu_msg_get_references (msg));
|
|
||||||
append_sexp_attr (gstr, "in-reply-to",
|
|
||||||
mu_msg_get_header (msg, "In-Reply-To"));
|
|
||||||
|
|
||||||
/* headers are retrieved from the database, views from the
|
|
||||||
* message file file attr things can only be gotten from the
|
|
||||||
* file (ie., mu view), not from the database (mu find). */
|
|
||||||
if (!(opts & MU_MSG_OPTION_HEADERS_ONLY))
|
|
||||||
append_message_file_parts (gstr, msg, opts);
|
|
||||||
|
|
||||||
g_string_append (gstr, ")\n");
|
|
||||||
return g_string_free (gstr, FALSE);
|
|
||||||
}
|
|
||||||
470
lib/mu-msg-sexp.cc
Normal file
470
lib/mu-msg-sexp.cc
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2011-2020 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
|
||||||
|
** Free Software Foundation; either version 3, or (at your option) any
|
||||||
|
** later version.
|
||||||
|
**
|
||||||
|
** This program is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
** GNU General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License
|
||||||
|
** along with this program; if not, write to the Free Software Foundation,
|
||||||
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "utils/mu-str.h"
|
||||||
|
#include "mu-msg.h"
|
||||||
|
#include "mu-msg-iter.h"
|
||||||
|
#include "mu-msg-part.h"
|
||||||
|
#include "mu-maildir.h"
|
||||||
|
|
||||||
|
using namespace Mu;
|
||||||
|
using namespace Sexp;
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_prop_nonempty (Node::Seq& items, const char* elm, const GSList *str_lst)
|
||||||
|
{
|
||||||
|
Node::Seq elms;
|
||||||
|
while (str_lst) {
|
||||||
|
elms.add((const char*)str_lst->data);
|
||||||
|
str_lst = g_slist_next(str_lst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!elms.empty())
|
||||||
|
items.add_prop(elm, elms);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_prop_nonempty (Node::Seq& items, const char* elm, Node::Seq&& seq)
|
||||||
|
{
|
||||||
|
if (!seq.empty())
|
||||||
|
items.add_prop(elm, std::move(seq));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_prop_nonempty (Node::Seq& items, const char* name, const char *str)
|
||||||
|
{
|
||||||
|
if (str && str[0])
|
||||||
|
items.add_prop(name, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_prop_symbol (Node::Seq& items, const char* name, const char *str)
|
||||||
|
{
|
||||||
|
items.add_prop(name, Node::make_symbol(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Node
|
||||||
|
make_contact_node (MuMsgContact *c)
|
||||||
|
{
|
||||||
|
// a cons-pair...perhaps make this a plist too?
|
||||||
|
|
||||||
|
Node::Seq contact;
|
||||||
|
if (mu_msg_contact_name(c))
|
||||||
|
contact.add (mu_msg_contact_name(c));
|
||||||
|
else
|
||||||
|
contact.add (Node::make_symbol("nil"));
|
||||||
|
|
||||||
|
contact.add(Node::make_symbol("."));
|
||||||
|
contact.add(mu_msg_contact_email(c));
|
||||||
|
|
||||||
|
return Node::make_list(std::move(contact));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_list_post (Node::Seq& items, MuMsg *msg)
|
||||||
|
{
|
||||||
|
/* some mailing lists do not set the reply-to; see pull #1278. So for
|
||||||
|
* those cases, check the List-Post address and use that instead */
|
||||||
|
|
||||||
|
GMatchInfo *minfo;
|
||||||
|
GRegex *rx;
|
||||||
|
const char* list_post;
|
||||||
|
|
||||||
|
list_post = mu_msg_get_header (msg, "List-Post");
|
||||||
|
if (!list_post)
|
||||||
|
return;
|
||||||
|
|
||||||
|
rx = g_regex_new ("<?mailto:([a-z0-9%+@._-]+)>?", G_REGEX_CASELESS,
|
||||||
|
(GRegexMatchFlags)0, NULL);
|
||||||
|
g_return_if_fail(rx);
|
||||||
|
|
||||||
|
Node::Seq addrs;
|
||||||
|
if (g_regex_match (rx, list_post, (GRegexMatchFlags)0, &minfo)) {
|
||||||
|
auto address = (char*)g_match_info_fetch (minfo, 1);
|
||||||
|
MuMsgContact contact{NULL, address};
|
||||||
|
addrs.add(make_contact_node(&contact));
|
||||||
|
items.add_prop(":list-post", std::move(addrs));
|
||||||
|
g_free(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_match_info_free (minfo);
|
||||||
|
g_regex_unref (rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct _ContactData {
|
||||||
|
Node::Seq from, to, cc, bcc, reply_to;
|
||||||
|
};
|
||||||
|
typedef struct _ContactData ContactData;
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
each_contact (MuMsgContact *c, ContactData *cdata)
|
||||||
|
{
|
||||||
|
switch (mu_msg_contact_type (c)) {
|
||||||
|
|
||||||
|
case MU_MSG_CONTACT_TYPE_FROM:
|
||||||
|
cdata->from.add(make_contact_node(c));
|
||||||
|
break;
|
||||||
|
case MU_MSG_CONTACT_TYPE_TO:
|
||||||
|
cdata->to.add(make_contact_node(c));
|
||||||
|
break;
|
||||||
|
case MU_MSG_CONTACT_TYPE_CC:
|
||||||
|
cdata->cc.add(make_contact_node(c));
|
||||||
|
break;
|
||||||
|
case MU_MSG_CONTACT_TYPE_BCC:
|
||||||
|
cdata->bcc.add(make_contact_node(c));
|
||||||
|
break;
|
||||||
|
case MU_MSG_CONTACT_TYPE_REPLY_TO:
|
||||||
|
cdata->reply_to.add(make_contact_node(c));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g_return_val_if_reached (FALSE);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_contacts (Node::Seq& items, MuMsg *msg)
|
||||||
|
{
|
||||||
|
ContactData cdata{};
|
||||||
|
mu_msg_contact_foreach (msg, (MuMsgContactForeachFunc)each_contact,
|
||||||
|
&cdata);
|
||||||
|
|
||||||
|
add_prop_nonempty(items, ":from", std::move(cdata.from));
|
||||||
|
add_prop_nonempty(items, ":to", std::move(cdata.to));
|
||||||
|
add_prop_nonempty(items, ":cc", std::move(cdata.cc));
|
||||||
|
add_prop_nonempty(items, ":bcc", std::move(cdata.bcc));
|
||||||
|
add_prop_nonempty(items, ":reply-to", std::move(cdata.reply_to));
|
||||||
|
|
||||||
|
add_list_post (items, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Node::Seq flagseq;
|
||||||
|
MuFlags msgflags;
|
||||||
|
} FlagData;
|
||||||
|
|
||||||
|
static void
|
||||||
|
each_flag (MuFlags flag, FlagData *fdata)
|
||||||
|
{
|
||||||
|
if (flag & fdata->msgflags)
|
||||||
|
fdata->flagseq.add(Node::make_symbol(mu_flag_name(flag)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_flags (Node::Seq& items, MuMsg *msg)
|
||||||
|
{
|
||||||
|
FlagData fdata{};
|
||||||
|
fdata.msgflags = mu_msg_get_flags (msg);
|
||||||
|
|
||||||
|
mu_flags_foreach ((MuFlagsForeachFunc)each_flag, &fdata);
|
||||||
|
add_prop_nonempty(items, ":flags", std::move(fdata.flagseq));
|
||||||
|
}
|
||||||
|
|
||||||
|
static char*
|
||||||
|
get_temp_file (MuMsg *msg, MuMsgOptions opts, unsigned index)
|
||||||
|
{
|
||||||
|
char *path;
|
||||||
|
GError *err;
|
||||||
|
|
||||||
|
err = NULL;
|
||||||
|
path = mu_msg_part_get_cache_path (msg, opts, index, &err);
|
||||||
|
if (!path)
|
||||||
|
goto errexit;
|
||||||
|
|
||||||
|
if (!mu_msg_part_save (msg, opts, path, index, &err))
|
||||||
|
goto errexit;
|
||||||
|
|
||||||
|
return path;
|
||||||
|
|
||||||
|
errexit:
|
||||||
|
g_warning ("failed to save mime part: %s",
|
||||||
|
err->message ? err->message : "something went wrong");
|
||||||
|
g_clear_error (&err);
|
||||||
|
g_free (path);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gchar*
|
||||||
|
get_temp_file_maybe (MuMsg *msg, MuMsgPart *part, MuMsgOptions opts)
|
||||||
|
{
|
||||||
|
opts = (MuMsgOptions)((int)opts | (int)MU_MSG_OPTION_USE_EXISTING);
|
||||||
|
if (!(opts & MU_MSG_OPTION_EXTRACT_IMAGES) ||
|
||||||
|
g_ascii_strcasecmp (part->type, "image") != 0)
|
||||||
|
return NULL;
|
||||||
|
else
|
||||||
|
return get_temp_file (msg, opts, part->index);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PartInfo {
|
||||||
|
Node::Seq parts;
|
||||||
|
MuMsgOptions opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
sig_verdict (Node::Seq& partseq, MuMsgPart *mpart)
|
||||||
|
{
|
||||||
|
MuMsgPartSigStatusReport *report = mpart->sig_status_report;
|
||||||
|
if (!report)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||||
|
switch (report->verdict) {
|
||||||
|
case MU_MSG_PART_SIG_STATUS_GOOD:
|
||||||
|
add_prop_symbol (partseq, ":signature", "verified");
|
||||||
|
break;
|
||||||
|
case MU_MSG_PART_SIG_STATUS_BAD:
|
||||||
|
add_prop_symbol (partseq, ":signature", "bad");
|
||||||
|
break;
|
||||||
|
case MU_MSG_PART_SIG_STATUS_ERROR:
|
||||||
|
add_prop_symbol (partseq, ":signature", "unverified");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
add_prop_nonempty (partseq, ":signers", report->signers);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dec_verdict (Node::Seq& partseq, MuMsgPart *mpart)
|
||||||
|
{
|
||||||
|
if (mpart->part_type & MU_MSG_PART_TYPE_DECRYPTED)
|
||||||
|
partseq.add_prop(":decryption", Node::make_symbol("succeeded"));
|
||||||
|
else if (mpart->part_type & MU_MSG_PART_TYPE_ENCRYPTED)
|
||||||
|
partseq.add_prop(":decryption", Node::make_symbol("failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Node::Seq
|
||||||
|
make_part_types (MuMsgPartType ptype)
|
||||||
|
{
|
||||||
|
struct PartTypes {
|
||||||
|
MuMsgPartType ptype;
|
||||||
|
const char* name;
|
||||||
|
} ptypes[] = {
|
||||||
|
{ MU_MSG_PART_TYPE_LEAF, "leaf" },
|
||||||
|
{ MU_MSG_PART_TYPE_MESSAGE, "message" },
|
||||||
|
{ MU_MSG_PART_TYPE_INLINE, "inline" },
|
||||||
|
{ MU_MSG_PART_TYPE_ATTACHMENT, "attachment" },
|
||||||
|
{ MU_MSG_PART_TYPE_SIGNED, "signed" },
|
||||||
|
{ MU_MSG_PART_TYPE_ENCRYPTED, "encrypted" }
|
||||||
|
};
|
||||||
|
|
||||||
|
Node::Seq seq;
|
||||||
|
for (auto u = 0U; u!= G_N_ELEMENTS(ptypes); ++u)
|
||||||
|
if (ptype & ptypes[u].ptype)
|
||||||
|
seq.add(Node::make_symbol(ptypes[u].name));
|
||||||
|
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
each_part (MuMsg *msg, MuMsgPart *part, PartInfo *pinfo)
|
||||||
|
{
|
||||||
|
auto mimetype = format("%s/%s",
|
||||||
|
part->type ? part->type : "application",
|
||||||
|
part->subtype ? part->subtype : "octet-stream");
|
||||||
|
auto maybe_attach = Node::make_symbol(mu_msg_part_maybe_attachment (part) ?
|
||||||
|
"t" : "nil");
|
||||||
|
|
||||||
|
Node::Seq partseq;
|
||||||
|
|
||||||
|
partseq.add_prop(":index", part->index);
|
||||||
|
partseq.add_prop(":mime-type", mimetype);
|
||||||
|
partseq.add_prop(":size", part->size);
|
||||||
|
|
||||||
|
dec_verdict (partseq, part);
|
||||||
|
sig_verdict (partseq, part);
|
||||||
|
|
||||||
|
add_prop_nonempty(partseq, ":type", make_part_types(part->part_type));
|
||||||
|
|
||||||
|
char *fname = mu_msg_part_get_filename (part, TRUE);
|
||||||
|
add_prop_nonempty(partseq, ":name", fname);
|
||||||
|
g_free (fname);
|
||||||
|
|
||||||
|
if (mu_msg_part_maybe_attachment (part))
|
||||||
|
add_prop_symbol (partseq, ":attachment", "t");
|
||||||
|
|
||||||
|
add_prop_nonempty (partseq, ":cid", mu_msg_part_get_content_id(part));
|
||||||
|
|
||||||
|
char *tempfile = get_temp_file_maybe (msg, part, pinfo->opts);
|
||||||
|
add_prop_nonempty (partseq, ":temp", tempfile);
|
||||||
|
g_free (tempfile);
|
||||||
|
|
||||||
|
pinfo->parts.add(Node::make_list(std::move(partseq)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_parts (Node::Seq& items, MuMsg *msg, MuMsgOptions opts)
|
||||||
|
{
|
||||||
|
PartInfo pinfo;
|
||||||
|
pinfo.opts = opts;
|
||||||
|
|
||||||
|
if (mu_msg_part_foreach (msg, opts, (MuMsgPartForeachFunc)each_part, &pinfo))
|
||||||
|
add_prop_nonempty (items, ":parts", std::move(pinfo.parts));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_thread_info (Node::Seq& items, const MuMsgIterThreadInfo *ti)
|
||||||
|
{
|
||||||
|
Node::Seq info;
|
||||||
|
|
||||||
|
info.add_prop(":path", ti->threadpath);
|
||||||
|
info.add_prop(":level", ti->level);
|
||||||
|
|
||||||
|
if (ti->prop & MU_MSG_ITER_THREAD_PROP_FIRST_CHILD)
|
||||||
|
add_prop_symbol(info, ":first-child", "t");
|
||||||
|
if (ti->prop & MU_MSG_ITER_THREAD_PROP_LAST_CHILD)
|
||||||
|
add_prop_symbol(info, ":last-child", "t");
|
||||||
|
if (ti->prop & MU_MSG_ITER_THREAD_PROP_EMPTY_PARENT)
|
||||||
|
add_prop_symbol(info, ":empty-parent", "t");
|
||||||
|
if (ti->prop & MU_MSG_ITER_THREAD_PROP_DUP)
|
||||||
|
add_prop_symbol(info, ":duplicate", "t");
|
||||||
|
if (ti->prop & MU_MSG_ITER_THREAD_PROP_HAS_CHILD)
|
||||||
|
add_prop_symbol(info, ":has-child", "t");
|
||||||
|
|
||||||
|
items.add_prop(":thread", std::move(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_message_file_parts (Node::Seq& items, MuMsg *msg, MuMsgOptions opts)
|
||||||
|
{
|
||||||
|
GError *err{NULL};
|
||||||
|
if (!mu_msg_load_msg_file (msg, &err)) {
|
||||||
|
g_warning ("failed to load message file: %s",
|
||||||
|
err ? err->message : "some error occurred");
|
||||||
|
g_clear_error (&err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_parts (items, msg, opts);
|
||||||
|
add_contacts (items, msg);
|
||||||
|
|
||||||
|
/* add the user-agent / x-mailer */
|
||||||
|
auto str = mu_msg_get_header (msg, "User-Agent");
|
||||||
|
if (!str)
|
||||||
|
str = mu_msg_get_header (msg, "X-Mailer");
|
||||||
|
|
||||||
|
add_prop_nonempty (items, ":user-agent", str);
|
||||||
|
add_prop_nonempty (items, ":body-txt-params",
|
||||||
|
mu_msg_get_body_text_content_type_parameters (msg, opts));
|
||||||
|
|
||||||
|
add_prop_nonempty (items, ":body-txt", mu_msg_get_body_text(msg, opts));
|
||||||
|
add_prop_nonempty (items, ":body-html", mu_msg_get_body_html(msg, opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_date_and_size (Node::Seq& items, MuMsg *msg)
|
||||||
|
{
|
||||||
|
auto t = mu_msg_get_date (msg);
|
||||||
|
if (t == (time_t)-1) /* invalid date? */
|
||||||
|
t = 0;
|
||||||
|
|
||||||
|
Node::Seq dseq;
|
||||||
|
dseq.add((unsigned)(t >> 16));
|
||||||
|
dseq.add((unsigned)(t && 0xffff));
|
||||||
|
dseq.add(0);
|
||||||
|
|
||||||
|
items.add_prop(":date", std::move(dseq));
|
||||||
|
|
||||||
|
auto s = mu_msg_get_size (msg);
|
||||||
|
if (s == (size_t)-1) /* invalid size? */
|
||||||
|
s = 0;
|
||||||
|
|
||||||
|
items.add_prop(":size", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_tags (Node::Seq& items, MuMsg *msg)
|
||||||
|
{
|
||||||
|
Node::Seq tagseq;
|
||||||
|
for (auto tags = mu_msg_get_tags(msg); tags; tags = g_slist_next(tags))
|
||||||
|
tagseq.add((const char*)tags->data);
|
||||||
|
|
||||||
|
add_prop_nonempty (items, ":tags", std::move(tagseq));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Mu::Sexp::Node
|
||||||
|
Mu::msg_to_sexp (MuMsg *msg, unsigned docid,
|
||||||
|
const struct _MuMsgIterThreadInfo *ti,
|
||||||
|
MuMsgOptions opts)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (msg, Sexp::Node::make("error"));
|
||||||
|
g_return_val_if_fail (!((opts & MU_MSG_OPTION_HEADERS_ONLY) &&
|
||||||
|
(opts & MU_MSG_OPTION_EXTRACT_IMAGES)),
|
||||||
|
Sexp::Node::make("error"));
|
||||||
|
Node::Seq items;
|
||||||
|
|
||||||
|
if (docid != 0)
|
||||||
|
items.add_prop(":docid", docid);
|
||||||
|
|
||||||
|
if (ti)
|
||||||
|
add_thread_info (items, ti);
|
||||||
|
|
||||||
|
add_prop_nonempty (items, ":subject", mu_msg_get_subject (msg));
|
||||||
|
add_prop_nonempty (items, ":message-id", mu_msg_get_msgid (msg));
|
||||||
|
add_prop_nonempty (items, ":mailing-list", mu_msg_get_mailing_list (msg));
|
||||||
|
add_prop_nonempty (items, ":path", mu_msg_get_path (msg));
|
||||||
|
add_prop_nonempty (items, ":maildir", mu_msg_get_maildir (msg));
|
||||||
|
|
||||||
|
items.add_prop(":priority", Node::make_symbol(mu_msg_prio_name(mu_msg_get_prio(msg))));
|
||||||
|
|
||||||
|
/* in the no-headers-only case (see below) we get a more complete list of contacts, so no
|
||||||
|
* need to get them here if that's the case */
|
||||||
|
if (opts & MU_MSG_OPTION_HEADERS_ONLY)
|
||||||
|
add_contacts (items, msg);
|
||||||
|
|
||||||
|
add_prop_nonempty (items, ":references", mu_msg_get_references (msg));
|
||||||
|
add_prop_nonempty (items, ":in-reply-to", mu_msg_get_header (msg, "In-Reply-To"));
|
||||||
|
|
||||||
|
add_date_and_size (items, msg);
|
||||||
|
add_flags (items, msg);
|
||||||
|
add_tags (items, msg);
|
||||||
|
|
||||||
|
/* headers are retrieved from the database, views from the
|
||||||
|
* message file file attr things can only be gotten from the
|
||||||
|
* file (ie., mu view), not from the database (mu find). */
|
||||||
|
if (!(opts & MU_MSG_OPTION_HEADERS_ONLY))
|
||||||
|
add_message_file_parts (items, msg, opts);
|
||||||
|
|
||||||
|
return Node::make_list(std::move(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char*
|
||||||
|
mu_msg_to_sexp (MuMsg *msg, unsigned docid, const MuMsgIterThreadInfo *ti,
|
||||||
|
MuMsgOptions opts)
|
||||||
|
{
|
||||||
|
return g_strdup (Mu::msg_to_sexp (msg, docid, ti, opts)
|
||||||
|
.to_string().c_str());
|
||||||
|
}
|
||||||
157
lib/mu-msg.h
157
lib/mu-msg.h
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
/* -*- mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
/* -*- mode: c; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
||||||
**
|
**
|
||||||
** Copyright (C) 2010-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
** Copyright (C) 2010-2013 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
@ -32,7 +33,7 @@ struct _MuMsg;
|
|||||||
typedef struct _MuMsg MuMsg;
|
typedef struct _MuMsg MuMsg;
|
||||||
|
|
||||||
/* options for various functions */
|
/* options for various functions */
|
||||||
enum _MuMsgOptions {
|
typedef enum {
|
||||||
MU_MSG_OPTION_NONE = 0,
|
MU_MSG_OPTION_NONE = 0,
|
||||||
/* 1 << 0 is still free! */
|
/* 1 << 0 is still free! */
|
||||||
|
|
||||||
@ -59,10 +60,7 @@ enum _MuMsgOptions {
|
|||||||
/* recurse into submessages */
|
/* recurse into submessages */
|
||||||
MU_MSG_OPTION_RECURSE_RFC822 = 1 << 11
|
MU_MSG_OPTION_RECURSE_RFC822 = 1 << 11
|
||||||
|
|
||||||
};
|
} MuMsgOptions;
|
||||||
typedef enum _MuMsgOptions MuMsgOptions;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a new MuMsg* instance which parses a message and provides
|
* create a new MuMsg* instance which parses a message and provides
|
||||||
@ -435,59 +433,6 @@ int mu_msg_cmp (MuMsg *m1, MuMsg *m2, MuMsgFieldId mfid);
|
|||||||
gboolean mu_msg_is_readable (MuMsg *self);
|
gboolean mu_msg_is_readable (MuMsg *self);
|
||||||
|
|
||||||
|
|
||||||
struct _MuMsgIterThreadInfo;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert the msg to a Lisp symbolic expression (for further processing in
|
|
||||||
* e.g. emacs)
|
|
||||||
*
|
|
||||||
* @param msg a valid message
|
|
||||||
* @param docid the docid for this message, or 0
|
|
||||||
* @param ti thread info for the current message, or NULL
|
|
||||||
* @param opts, bitwise OR'ed;
|
|
||||||
* - MU_MSG_OPTION_HEADERS_ONLY: only include message fields which can be
|
|
||||||
* obtained from the database (this is much faster if the MuMsg is
|
|
||||||
* database-backed, so no file needs to be opened)
|
|
||||||
* - MU_MSG_OPTION_EXTRACT_IMAGES: extract image attachments as temporary
|
|
||||||
* files and include links to those in the sexp
|
|
||||||
* and for message parts:
|
|
||||||
* MU_MSG_OPTION_CHECK_SIGNATURES: check signatures
|
|
||||||
* MU_MSG_OPTION_AUTO_RETRIEVE_KEY: attempt to retrieve keys online
|
|
||||||
* MU_MSG_OPTION_USE_AGENT: attempt to use GPG-agent
|
|
||||||
* MU_MSG_OPTION_USE_PKCS7: attempt to use PKCS (instead of gpg)
|
|
||||||
*
|
|
||||||
* @return a string with the sexp (free with g_free) or NULL in case of error
|
|
||||||
*/
|
|
||||||
char* mu_msg_to_sexp (MuMsg *msg, unsigned docid,
|
|
||||||
const struct _MuMsgIterThreadInfo *ti,
|
|
||||||
MuMsgOptions ops)
|
|
||||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
|
||||||
|
|
||||||
#ifdef HAVE_JSON_GLIB
|
|
||||||
|
|
||||||
struct _JsonNode; /* forward declaration */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert the msg to json
|
|
||||||
*
|
|
||||||
* @param msg a valid message
|
|
||||||
* @param docid the docid for this message, or 0
|
|
||||||
* @param ti thread info for the current message, or NULL
|
|
||||||
* @param opts, bitwise OR'ed;
|
|
||||||
* - MU_MSG_OPTION_HEADERS_ONLY: only include message fields which can be
|
|
||||||
* obtained from the database (this is much faster if the MuMsg is
|
|
||||||
* database-backed, so no file needs to be opened)
|
|
||||||
* - MU_MSG_OPTION_EXTRACT_IMAGES: extract image attachments as temporary
|
|
||||||
* files and include links to those in the sexp
|
|
||||||
*
|
|
||||||
* @return a string with the sexp (free with g_free) or NULL in case of error
|
|
||||||
*/
|
|
||||||
struct _JsonNode* mu_msg_to_json (MuMsg *msg, unsigned docid,
|
|
||||||
const struct _MuMsgIterThreadInfo *ti,
|
|
||||||
MuMsgOptions ops) G_GNUC_WARN_UNUSED_RESULT;
|
|
||||||
#endif /*HAVE_JSON_GLIB*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* move a message to another maildir; note that this does _not_ update
|
* move a message to another maildir; note that this does _not_ update
|
||||||
* the database
|
* the database
|
||||||
@ -544,14 +489,13 @@ typedef guint MuMsgContactType;
|
|||||||
#define mu_msg_contact_type_is_valid(MCT)\
|
#define mu_msg_contact_type_is_valid(MCT)\
|
||||||
((MCT) < MU_MSG_CONTACT_TYPE_NUM)
|
((MCT) < MU_MSG_CONTACT_TYPE_NUM)
|
||||||
|
|
||||||
struct _MuMsgContact {
|
typedef struct {
|
||||||
const char *name; /**< Foo Bar */
|
const char *name; /**< Foo Bar */
|
||||||
const char *email; /**< foo@bar.cuux */
|
const char *email; /**< foo@bar.cuux */
|
||||||
const char *full_address; /**< Foo Bar <foo@bar.cuux> */
|
const char *full_address; /**< Foo Bar <foo@bar.cuux> */
|
||||||
MuMsgContactType type; /**< MU_MSG_CONTACT_TYPE_{ TO,
|
MuMsgContactType type; /**< MU_MSG_CONTACT_TYPE_{ TO,
|
||||||
CC, BCC, FROM, REPLY_TO} */
|
CC, BCC, FROM, REPLY_TO} */
|
||||||
};
|
} MuMsgContact;
|
||||||
typedef struct _MuMsgContact MuMsgContact;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -649,6 +593,97 @@ char* mu_str_flags (MuFlags flags)
|
|||||||
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
|
||||||
|
struct _MuMsgIterThreadInfo;
|
||||||
|
|
||||||
|
#ifdef HAVE_JSON_GLIB
|
||||||
|
|
||||||
|
struct _JsonNode; /* forward declaration */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert the msg to json
|
||||||
|
*
|
||||||
|
* @param msg a valid message
|
||||||
|
* @param docid the docid for this message, or 0
|
||||||
|
* @param ti thread info for the current message, or NULL
|
||||||
|
* @param opts, bitwise OR'ed;
|
||||||
|
* - MU_MSG_OPTION_HEADERS_ONLY: only include message fields which can be
|
||||||
|
* obtained from the database (this is much faster if the MuMsg is
|
||||||
|
* database-backed, so no file needs to be opened)
|
||||||
|
* - MU_MSG_OPTION_EXTRACT_IMAGES: extract image attachments as temporary
|
||||||
|
* files and include links to those in the sexp
|
||||||
|
*
|
||||||
|
* @return a string with the sexp (free with g_free) or NULL in case of error
|
||||||
|
*/
|
||||||
|
struct _JsonNode* mu_msg_to_json (MuMsg *msg, unsigned docid,
|
||||||
|
const struct _MuMsgIterThreadInfo *ti,
|
||||||
|
MuMsgOptions ops) G_GNUC_WARN_UNUSED_RESULT;
|
||||||
|
#endif /*HAVE_JSON_GLIB*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert the msg to a Lisp symbolic expression (for further processing in
|
||||||
|
* e.g. emacs)
|
||||||
|
*
|
||||||
|
* @param msg a valid message
|
||||||
|
* @param docid the docid for this message, or 0
|
||||||
|
* @param ti thread info for the current message, or NULL
|
||||||
|
* @param opts, bitwise OR'ed;
|
||||||
|
* - MU_MSG_OPTION_HEADERS_ONLY: only include message fields which can be
|
||||||
|
* obtained from the database (this is much faster if the MuMsg is
|
||||||
|
* database-backed, so no file needs to be opened)
|
||||||
|
* - MU_MSG_OPTION_EXTRACT_IMAGES: extract image attachments as temporary
|
||||||
|
* files and include links to those in the sexp
|
||||||
|
* and for message parts:
|
||||||
|
* MU_MSG_OPTION_CHECK_SIGNATURES: check signatures
|
||||||
|
* MU_MSG_OPTION_AUTO_RETRIEVE_KEY: attempt to retrieve keys online
|
||||||
|
* MU_MSG_OPTION_USE_AGENT: attempt to use GPG-agent
|
||||||
|
* MU_MSG_OPTION_USE_PKCS7: attempt to use PKCS (instead of gpg)
|
||||||
|
*
|
||||||
|
* @return a string with the sexp (free with g_free) or NULL in case of error
|
||||||
|
*/
|
||||||
|
char* mu_msg_to_sexp (MuMsg *msg, unsigned docid,
|
||||||
|
const struct _MuMsgIterThreadInfo *ti,
|
||||||
|
MuMsgOptions ops)
|
||||||
|
G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <utils/mu-sexp.hh>
|
||||||
|
|
||||||
|
namespace Mu {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert the msg to a Lisp symbolic expression (for further processing in
|
||||||
|
* e.g. emacs)
|
||||||
|
*
|
||||||
|
* @param msg a valid message
|
||||||
|
* @param docid the docid for this message, or 0
|
||||||
|
* @param ti thread info for the current message, or NULL
|
||||||
|
* @param opts, bitwise OR'ed;
|
||||||
|
* - MU_MSG_OPTION_HEADERS_ONLY: only include message fields which can be
|
||||||
|
* obtained from the database (this is much faster if the MuMsg is
|
||||||
|
* database-backed, so no file needs to be opened)
|
||||||
|
* - MU_MSG_OPTION_EXTRACT_IMAGES: extract image attachments as temporary
|
||||||
|
* files and include links to those in the sexp
|
||||||
|
* and for message parts:
|
||||||
|
* MU_MSG_OPTION_CHECK_SIGNATURES: check signatures
|
||||||
|
* MU_MSG_OPTION_AUTO_RETRIEVE_KEY: attempt to retrieve keys online
|
||||||
|
* MU_MSG_OPTION_USE_AGENT: attempt to use GPG-agent
|
||||||
|
* MU_MSG_OPTION_USE_PKCS7: attempt to use PKCS (instead of gpg)
|
||||||
|
*
|
||||||
|
* @return a Sexp::Node representing the message
|
||||||
|
*/
|
||||||
|
Mu::Sexp::Node msg_to_sexp (MuMsg *msg, unsigned docid,
|
||||||
|
const struct _MuMsgIterThreadInfo *ti,
|
||||||
|
MuMsgOptions ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /*__cplusplus*/
|
||||||
|
|
||||||
|
|
||||||
#endif /*__MU_MSG_H__*/
|
#endif /*__MU_MSG_H__*/
|
||||||
|
|||||||
@ -55,8 +55,8 @@ libmu_utils_la_SOURCES= \
|
|||||||
mu-command-parser.hh \
|
mu-command-parser.hh \
|
||||||
mu-readline.cc \
|
mu-readline.cc \
|
||||||
mu-readline.hh \
|
mu-readline.hh \
|
||||||
mu-sexp-parser.cc \
|
mu-sexp.cc \
|
||||||
mu-sexp-parser.hh \
|
mu-sexp.hh \
|
||||||
mu-str.c \
|
mu-str.c \
|
||||||
mu-str.h \
|
mu-str.h \
|
||||||
mu-util.c \
|
mu-util.c \
|
||||||
@ -94,10 +94,10 @@ test_mu_str_LDADD= \
|
|||||||
libmu-utils.la
|
libmu-utils.la
|
||||||
|
|
||||||
TEST_PROGS+= \
|
TEST_PROGS+= \
|
||||||
test-sexp-parser
|
test-sexp
|
||||||
test_sexp_parser_SOURCES= \
|
test_sexp_SOURCES= \
|
||||||
test-sexp-parser.cc
|
test-sexp.cc
|
||||||
test_sexp_parser_LDADD= \
|
test_sexp_LDADD= \
|
||||||
libmu-utils.la
|
libmu-utils.la
|
||||||
|
|
||||||
TEST_PROGS+= \
|
TEST_PROGS+= \
|
||||||
|
|||||||
@ -27,24 +27,25 @@ using namespace Mu;
|
|||||||
using namespace Command;
|
using namespace Command;
|
||||||
using namespace Sexp;
|
using namespace Sexp;
|
||||||
|
|
||||||
|
using Type = Node::Type;
|
||||||
|
|
||||||
static Mu::Error
|
static Mu::Error
|
||||||
command_error(const std::string& msg)
|
command_error(const std::string& msg)
|
||||||
{
|
{
|
||||||
return Mu::Error(Error::Code::Command, msg);
|
return Mu::Error(Error::Code::Command, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Command::invoke(const Command::CommandMap& cmap, const Node& call)
|
Command::invoke(const Command::CommandMap& cmap, const Node& call)
|
||||||
{
|
{
|
||||||
if (call.type != Type::List || call.children.empty() ||
|
if (call.type() != Type::List || call.elements().empty() ||
|
||||||
call.children[0].type != Type::Symbol)
|
call.elements().at(0).type() != Type::Symbol)
|
||||||
throw command_error("call must be a list starting with a symbol");
|
throw command_error("call must be a list starting with a symbol");
|
||||||
|
|
||||||
const auto& params{call.children};
|
const auto& params{call.elements()};
|
||||||
const auto cmd_it = cmap.find(params[0].value);
|
const auto cmd_it = cmap.find(params.at(0).value());
|
||||||
if (cmd_it == cmap.end())
|
if (cmd_it == cmap.end())
|
||||||
throw command_error("unknown command '" + params[0].value + "'");
|
throw command_error("unknown command '" + params.at(0).value() + "'");
|
||||||
|
|
||||||
const auto& cinfo{cmd_it->second};
|
const auto& cinfo{cmd_it->second};
|
||||||
|
|
||||||
@ -58,8 +59,8 @@ Command::invoke(const Command::CommandMap& cmap, const Node& call)
|
|||||||
// so, we're looking for the odd-numbered parameters.
|
// so, we're looking for the odd-numbered parameters.
|
||||||
const auto param_it = [&]() {
|
const auto param_it = [&]() {
|
||||||
for (size_t i = 1; i < params.size(); i += 2)
|
for (size_t i = 1; i < params.size(); i += 2)
|
||||||
if (params[i].type == Type::Symbol &&
|
if (params.at(i).type() == Type::Symbol &&
|
||||||
params[i].value == ':' + argname)
|
params.at(i).value() == ':' + argname)
|
||||||
return params.begin() + i + 1;
|
return params.begin() + i + 1;
|
||||||
|
|
||||||
return params.end();
|
return params.end();
|
||||||
@ -75,31 +76,31 @@ Command::invoke(const Command::CommandMap& cmap, const Node& call)
|
|||||||
|
|
||||||
// the types must match, but the 'nil' symbol is acceptable as
|
// the types must match, but the 'nil' symbol is acceptable as
|
||||||
// "no value"
|
// "no value"
|
||||||
if (param_it->type != arginfo.type &&
|
if (param_it->type() != arginfo.type &&
|
||||||
!(param_it->type == Type::Symbol && param_it->value == "nil"))
|
!(param_it->type() == Type::Symbol && param_it->value() == "nil"))
|
||||||
throw command_error("parameter '" + argname + "' expects type " +
|
throw command_error("parameter '" + argname + "' expects type " +
|
||||||
to_string(arginfo.type) +
|
to_string(arginfo.type) +
|
||||||
" but got " + to_string(param_it->type));
|
" but got " + to_string(param_it->type()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// all passed parameters must be known
|
// all passed parameters must be known
|
||||||
for (size_t i = 1; i < params.size(); i += 2) {
|
for (size_t i = 1; i < params.size(); i += 2) {
|
||||||
if (std::none_of(cinfo.args.begin(), cinfo.args.end(),
|
if (std::none_of(cinfo.args.begin(), cinfo.args.end(),
|
||||||
[&](auto&& arg) {return params[i].value == ":" + arg.first;}))
|
[&](auto&& arg) {return params.at(i).value() == ":" + arg.first;}))
|
||||||
throw command_error("unknown parameter '" + params[i].value + "'");
|
throw command_error("unknown parameter '" + params.at(i).value() + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cinfo.handler)
|
if (cinfo.handler)
|
||||||
cinfo.handler(params);
|
cinfo.handler(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Parameters::const_iterator
|
static auto
|
||||||
find_param_node (const Parameters& params, const std::string& argname)
|
find_param_node (const Parameters& params, const std::string& argname)
|
||||||
{
|
{
|
||||||
for (size_t i = 1; i < params.size(); i += 2) {
|
for (size_t i = 1; i < params.size(); i += 2) {
|
||||||
if (i + 1 != params.size() &&
|
if (i + 1 != params.size() &&
|
||||||
params[i].type == Type::Symbol &&
|
params.at(i).type() == Type::Symbol &&
|
||||||
params[i].value == ':' + argname)
|
params.at(i).value() == ':' + argname)
|
||||||
return params.begin() + i + 1;
|
return params.begin() + i + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +112,7 @@ constexpr auto Nil = "nil";
|
|||||||
static bool
|
static bool
|
||||||
is_nil(const Node& node)
|
is_nil(const Node& node)
|
||||||
{
|
{
|
||||||
return node.type == Type::Symbol && node.value == Nil;
|
return node.type() == Type::Symbol && node.value() == Nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string&
|
const std::string&
|
||||||
@ -121,12 +122,12 @@ Command::get_string_or (const Parameters& params, const std::string& argname,
|
|||||||
const auto it = find_param_node (params, argname);
|
const auto it = find_param_node (params, argname);
|
||||||
if (it == params.end() || is_nil(*it))
|
if (it == params.end() || is_nil(*it))
|
||||||
return alt;
|
return alt;
|
||||||
else if (it->type != Type::String)
|
else if (it->type() != Type::String)
|
||||||
throw Error(Error::Code::InvalidArgument, "expected <string> but got %s (value: '%s')",
|
throw Error(Error::Code::InvalidArgument, "expected <string> but got %s (value: '%s')",
|
||||||
to_string(it->type).c_str(),
|
to_string(it->type()).c_str(),
|
||||||
it->value.c_str());
|
it->value().c_str());
|
||||||
|
|
||||||
return it->value;
|
return it->value();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string&
|
const std::string&
|
||||||
@ -136,12 +137,12 @@ Command::get_symbol_or (const Parameters& params, const std::string& argname,
|
|||||||
const auto it = find_param_node (params, argname);
|
const auto it = find_param_node (params, argname);
|
||||||
if (it == params.end() || is_nil(*it))
|
if (it == params.end() || is_nil(*it))
|
||||||
return alt;
|
return alt;
|
||||||
else if (it->type != Type::Symbol)
|
else if (it->type() != Type::Symbol)
|
||||||
throw Error(Error::Code::InvalidArgument, "expected <symbol> but got %s (value: '%s')",
|
throw Error(Error::Code::InvalidArgument, "expected <symbol> but got %s (value: '%s')",
|
||||||
to_string(it->type).c_str(),
|
to_string(it->type()).c_str(),
|
||||||
it->value.c_str());
|
it->value().c_str());
|
||||||
|
|
||||||
return it->value;
|
return it->value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -152,11 +153,11 @@ Command::get_int_or (const Parameters& params, const std::string& argname,
|
|||||||
const auto it = find_param_node (params, argname);
|
const auto it = find_param_node (params, argname);
|
||||||
if (it == params.end() || is_nil(*it))
|
if (it == params.end() || is_nil(*it))
|
||||||
return alt;
|
return alt;
|
||||||
else if (it->type != Type::Integer)
|
else if (it->type() != Type::Number)
|
||||||
throw Error(Error::Code::InvalidArgument, "expected <integer> but got %s",
|
throw Error(Error::Code::InvalidArgument, "expected <integer> but got %s",
|
||||||
to_string(it->type).c_str());
|
to_string(it->type()).c_str());
|
||||||
else
|
else
|
||||||
return ::atoi(it->value.c_str());
|
return ::atoi(it->value().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -166,11 +167,11 @@ Command::get_bool_or (const Parameters& params, const std::string& argname,
|
|||||||
const auto it = find_param_node (params, argname);
|
const auto it = find_param_node (params, argname);
|
||||||
if (it == params.end())
|
if (it == params.end())
|
||||||
return alt;
|
return alt;
|
||||||
else if (it->type != Type::Symbol)
|
else if (it->type() != Type::Symbol)
|
||||||
throw Error(Error::Code::InvalidArgument, "expected <symbol> but got %s",
|
throw Error(Error::Code::InvalidArgument, "expected <symbol> but got %s",
|
||||||
to_string(it->type).c_str());
|
to_string(it->type()).c_str());
|
||||||
else
|
else
|
||||||
return it->value != Nil;
|
return it->value() != Nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
@ -179,17 +180,17 @@ Command::get_string_vec (const Parameters& params, const std::string& argname)
|
|||||||
const auto it = find_param_node (params, argname);
|
const auto it = find_param_node (params, argname);
|
||||||
if (it == params.end() || is_nil(*it))
|
if (it == params.end() || is_nil(*it))
|
||||||
return {};
|
return {};
|
||||||
else if (it->type != Type::List)
|
else if (it->type() != Type::List)
|
||||||
throw Error(Error::Code::InvalidArgument, "expected <list> but got %s",
|
throw Error(Error::Code::InvalidArgument, "expected <list> but got %s",
|
||||||
to_string(it->type).c_str());
|
to_string(it->type()).c_str());
|
||||||
|
|
||||||
std::vector<std::string> vec;
|
std::vector<std::string> vec;
|
||||||
for (const auto& n: it->children) {
|
for (const auto& n: it->elements()) {
|
||||||
if (n.type != Type::String)
|
if (n.type() != Type::String)
|
||||||
throw Error(Error::Code::InvalidArgument,
|
throw Error(Error::Code::InvalidArgument,
|
||||||
"expected string element but got %s",
|
"expected string element but got %s",
|
||||||
to_string(n.type).c_str());
|
to_string(n.type()).c_str());
|
||||||
vec.emplace_back (n.value);
|
vec.emplace_back (n.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
return vec;
|
return vec;
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "utils/mu-error.hh"
|
#include "utils/mu-error.hh"
|
||||||
#include "utils/mu-sexp-parser.hh"
|
#include "utils/mu-sexp.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace Mu {
|
namespace Mu {
|
||||||
@ -49,10 +49,10 @@ namespace Command {
|
|||||||
|
|
||||||
/// Information about a function argument
|
/// Information about a function argument
|
||||||
struct ArgInfo {
|
struct ArgInfo {
|
||||||
ArgInfo (Sexp::Type typearg, bool requiredarg, std::string&& docarg):
|
ArgInfo (Sexp::Node::Type typearg, bool requiredarg, std::string&& docarg):
|
||||||
type{typearg}, required{requiredarg},docstring{std::move(docarg)}
|
type{typearg}, required{requiredarg},docstring{std::move(docarg)}
|
||||||
{}
|
{}
|
||||||
const Sexp::Type type; /**< Sexp::Type of the argument */
|
const Sexp::Node::Type type; /**< Sexp::Node::Type of the argument */
|
||||||
const bool required; /**< Is this argument required? */
|
const bool required; /**< Is this argument required? */
|
||||||
const std::string docstring; /**< Documentation */
|
const std::string docstring; /**< Documentation */
|
||||||
};
|
};
|
||||||
@ -60,7 +60,7 @@ struct ArgInfo {
|
|||||||
/// The arguments for a function, which maps their names to the information.
|
/// The arguments for a function, which maps their names to the information.
|
||||||
using ArgMap = std::unordered_map<std::string, ArgInfo>;
|
using ArgMap = std::unordered_map<std::string, ArgInfo>;
|
||||||
// The parameters to a Handler.
|
// The parameters to a Handler.
|
||||||
using Parameters = std::vector<Sexp::Node>;
|
using Parameters = Sexp::Node::Seq;
|
||||||
|
|
||||||
int get_int_or (const Parameters& parms, const std::string& argname, int alt=0);
|
int get_int_or (const Parameters& parms, const std::string& argname, int alt=0);
|
||||||
bool get_bool_or (const Parameters& parms, const std::string& argname, bool alt=false);
|
bool get_bool_or (const Parameters& parms, const std::string& argname, bool alt=false);
|
||||||
@ -149,7 +149,6 @@ operator<<(std::ostream& os, const Command::CommandMap& map)
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace Command
|
} // namespace Command
|
||||||
} // namespace Mu
|
} // namespace Mu
|
||||||
|
|
||||||
|
|||||||
@ -1,115 +0,0 @@
|
|||||||
/*
|
|
||||||
** Copyright (C) 2020 djcb <djcb@evergrey>
|
|
||||||
**
|
|
||||||
** 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
|
|
||||||
** Free Software Foundation; either version 3, or (at your option) any
|
|
||||||
** later version.
|
|
||||||
**
|
|
||||||
** This program is distributed in the hope that it will be useful,
|
|
||||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
** GNU General Public License for more details.
|
|
||||||
**
|
|
||||||
** You should have received a copy of the GNU General Public License
|
|
||||||
** along with this program; if not, write to the Free Software Foundation,
|
|
||||||
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
||||||
**
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MU_SEXP_PARSER_HH__
|
|
||||||
#define MU_SEXP_PARSER_HH__
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "utils/mu-error.hh"
|
|
||||||
|
|
||||||
namespace Mu {
|
|
||||||
namespace Sexp {
|
|
||||||
|
|
||||||
/// Simple s-expression parser that parses lists () and atoms (strings
|
|
||||||
/// ("-quoted), (positive) integers ([0..9]+) and symbol starting with alpha or
|
|
||||||
/// ':', then alphanum and '-')
|
|
||||||
///
|
|
||||||
/// (:foo (1234 "bar" nil) :quux (a b c))
|
|
||||||
|
|
||||||
/// Node type
|
|
||||||
enum struct Type { List, String, Integer, Symbol };
|
|
||||||
|
|
||||||
/// Parse node
|
|
||||||
struct Node {
|
|
||||||
/**
|
|
||||||
* Construct a new non-list node
|
|
||||||
*
|
|
||||||
* @param typearg the type of node
|
|
||||||
* @param valuearg the value
|
|
||||||
*/
|
|
||||||
Node(Type typearg, std::string&& valuearg):
|
|
||||||
type{typearg}, value{std::move(valuearg)} {
|
|
||||||
if (typearg == Type::List)
|
|
||||||
throw Error(Error::Code::Parsing,
|
|
||||||
"atomic type cannot be a <list>");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a list node
|
|
||||||
|
|
||||||
* @param childrenarg the list children
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
explicit Node(std::vector<Node>&& childrenarg):
|
|
||||||
type{Type::List}, children{std::move(childrenarg)}
|
|
||||||
{}
|
|
||||||
|
|
||||||
const Type type; /**< Type of node */
|
|
||||||
const std::string value; /**< String value of node (only for non-Type::List)*/
|
|
||||||
const std::vector<Node> children; /**< Chiidren of node (only for Type::List) */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the string as an s-expressi9on.
|
|
||||||
*
|
|
||||||
* @param expr an s-expression string
|
|
||||||
*
|
|
||||||
* @return the parsed s-expression, or throw Error.
|
|
||||||
*/
|
|
||||||
Node parse(const std::string& expr);
|
|
||||||
|
|
||||||
static inline std::ostream&
|
|
||||||
operator<<(std::ostream& os, Sexp::Type id)
|
|
||||||
{
|
|
||||||
switch (id) {
|
|
||||||
case Sexp::Type::List: os << "<list>"; break;
|
|
||||||
case Sexp::Type::String: os << "<string>"; break;
|
|
||||||
case Sexp::Type::Integer: os << "<integer>"; break;
|
|
||||||
case Sexp::Type::Symbol: os << "<symbol>"; break;
|
|
||||||
default: throw std::runtime_error ("unknown node type");
|
|
||||||
}
|
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline std::ostream&
|
|
||||||
operator<<(std::ostream& os, const Sexp::Node& node)
|
|
||||||
{
|
|
||||||
os << node.type;
|
|
||||||
if (node.type == Sexp::Type::List) {
|
|
||||||
os << '(';
|
|
||||||
for (auto&& elm: node.children)
|
|
||||||
os << elm;
|
|
||||||
os << ')';
|
|
||||||
} else
|
|
||||||
os << '{' << node.value << '}';
|
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // Sexp
|
|
||||||
|
|
||||||
|
|
||||||
} // Mu
|
|
||||||
|
|
||||||
#endif /* MU_SEXP_PARSER_HH__ */
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
** Copyright (C) 2020 djcb <djcb@evergrey>
|
** Copyright (C) 2020 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
|
||||||
@ -18,9 +19,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include "mu-sexp-parser.hh"
|
#include "mu-sexp.hh"
|
||||||
#include "mu-utils.hh"
|
#include "mu-utils.hh"
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
using namespace Mu;
|
using namespace Mu;
|
||||||
using namespace Sexp;
|
using namespace Sexp;
|
||||||
|
|
||||||
@ -58,16 +61,16 @@ parse_list (const std::string& expr, size_t& pos)
|
|||||||
if (expr[pos] != '(') // sanity check.
|
if (expr[pos] != '(') // sanity check.
|
||||||
throw parsing_error(pos, "expected: '(' but got '%c", expr[pos]);
|
throw parsing_error(pos, "expected: '(' but got '%c", expr[pos]);
|
||||||
|
|
||||||
std::vector<Node> children;
|
Node::Seq children;
|
||||||
|
|
||||||
++pos;
|
++pos;
|
||||||
while (expr[pos] != ')' && pos != expr.size())
|
while (expr[pos] != ')' && pos != expr.size())
|
||||||
children.emplace_back(parse(expr, pos));
|
children.add(parse(expr, pos));
|
||||||
|
|
||||||
if (expr[pos] != ')')
|
if (expr[pos] != ')')
|
||||||
throw parsing_error(pos, "expected: ')' but got '%c'", expr[pos]);
|
throw parsing_error(pos, "expected: ')' but got '%c'", expr[pos]);
|
||||||
++pos;
|
++pos;
|
||||||
return Node{std::move(children)};
|
return Node::make_list(std::move(children));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse string
|
// parse string
|
||||||
@ -100,7 +103,7 @@ parse_string (const std::string& expr, size_t& pos)
|
|||||||
throw parsing_error(pos, "unterminated string '%s'", str.c_str());
|
throw parsing_error(pos, "unterminated string '%s'", str.c_str());
|
||||||
|
|
||||||
++pos;
|
++pos;
|
||||||
return Node{Type::String, std::move(str)};
|
return Node::make_string(std::move(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Node
|
static Node
|
||||||
@ -118,7 +121,7 @@ parse_integer (const std::string& expr, size_t& pos)
|
|||||||
for (; isdigit(expr[pos]); ++pos)
|
for (; isdigit(expr[pos]); ++pos)
|
||||||
num += expr[pos];
|
num += expr[pos];
|
||||||
|
|
||||||
return Node {Type::Integer, std::move(num)};
|
return Node::make_number(::atoi(num.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static Node
|
static Node
|
||||||
@ -131,7 +134,7 @@ parse_symbol (const std::string& expr, size_t& pos)
|
|||||||
for (++pos; isalnum(expr[pos]) || expr[pos] == '-'; ++pos)
|
for (++pos; isalnum(expr[pos]) || expr[pos] == '-'; ++pos)
|
||||||
symbol += expr[pos];
|
symbol += expr[pos];
|
||||||
|
|
||||||
return Node { Type::Symbol, std::move(symbol)};
|
return Node::make_symbol(std::move(symbol));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -163,7 +166,7 @@ parse (const std::string& expr, size_t& pos)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Node
|
Node
|
||||||
Sexp::parse (const std::string& expr)
|
Sexp::Node::make (const std::string& expr)
|
||||||
{
|
{
|
||||||
size_t pos{};
|
size_t pos{};
|
||||||
auto node{::parse (expr, pos)};
|
auto node{::parse (expr, pos)};
|
||||||
@ -173,3 +176,42 @@ Sexp::parse (const std::string& expr)
|
|||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string
|
||||||
|
Sexp::Node::to_string () const
|
||||||
|
{
|
||||||
|
std::stringstream sstrm;
|
||||||
|
|
||||||
|
switch (type()) {
|
||||||
|
case Type::List: {
|
||||||
|
sstrm << '(';
|
||||||
|
bool first{true};
|
||||||
|
for (auto&& child : elements()) {
|
||||||
|
sstrm << (first ? "" : " ") << child.to_string();
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
sstrm << ')';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Type::String:
|
||||||
|
//sstrm << quote(value());
|
||||||
|
sstrm << "\"";
|
||||||
|
for (auto&& k: value()) {
|
||||||
|
switch (k) {
|
||||||
|
case '"' : sstrm << "\\\""; break;
|
||||||
|
case '\\': sstrm << "\\\\"; break;
|
||||||
|
default: sstrm << k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sstrm << "\"";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Type::Number:
|
||||||
|
case Type::Symbol:
|
||||||
|
default:
|
||||||
|
sstrm << value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sstrm.str();
|
||||||
|
}
|
||||||
252
lib/utils/mu-sexp.hh
Normal file
252
lib/utils/mu-sexp.hh
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2020 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
|
||||||
|
** Free Software Foundation; either version 3, or (at your option) any
|
||||||
|
** later version.
|
||||||
|
**
|
||||||
|
** This program is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
** GNU General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU General Public License
|
||||||
|
** along with this program; if not, write to the Free Software Foundation,
|
||||||
|
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef MU_SEXP_HH__
|
||||||
|
#define MU_SEXP_HH__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "utils/mu-utils.hh"
|
||||||
|
#include "utils/mu-error.hh"
|
||||||
|
|
||||||
|
namespace Mu {
|
||||||
|
namespace Sexp {
|
||||||
|
|
||||||
|
/// Simple s-expression parser & builder that parses lists () and atoms (strings
|
||||||
|
/// ("-quoted), (positive) integers ([0..9]+) and symbol starting with alpha or
|
||||||
|
/// ':', then alphanum and '-')
|
||||||
|
///
|
||||||
|
/// (:foo (1234 "bar" nil) :quux (a b c))
|
||||||
|
|
||||||
|
|
||||||
|
/// Parse node
|
||||||
|
struct Node {
|
||||||
|
/// Node type
|
||||||
|
enum struct Type { List, String, Number, Symbol };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a node of out of an s-expression string.
|
||||||
|
*
|
||||||
|
* @param expr a string containing an s-expression
|
||||||
|
*
|
||||||
|
* @return the parsed s-expression, or throw Error.
|
||||||
|
*/
|
||||||
|
static Node make (const std::string& expr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a node for a string/integer/symbol/list value
|
||||||
|
*
|
||||||
|
* @param val some value
|
||||||
|
*
|
||||||
|
* @return a node
|
||||||
|
*/
|
||||||
|
static Node make_string (std::string&& val) { return Node{Type::String, std::move(val)}; }
|
||||||
|
static Node make_string (const std::string& val) { return Node{Type::String, std::string{val}}; }
|
||||||
|
static Node make_number (int val) { return Node{Type::Number, format("%d", val)}; }
|
||||||
|
static Node make_symbol (std::string&& val) { return Node{Type::Symbol, std::move(val)}; }
|
||||||
|
|
||||||
|
|
||||||
|
/// sequence of node objects
|
||||||
|
struct Seq {
|
||||||
|
/**
|
||||||
|
* Add an item to a node-sequence.
|
||||||
|
*
|
||||||
|
* @param node item to add; moved/consumed.
|
||||||
|
*/
|
||||||
|
void add(Node&& node) {nodes_.emplace_back(std::move(node));}
|
||||||
|
void add(Seq&& seq) {add(make_list(std::move(seq)));}
|
||||||
|
void add(std::string&& s) {add(make_string(std::move(s)));}
|
||||||
|
void add(int i) {add(make_number(i));}
|
||||||
|
// disambiguate.
|
||||||
|
void add_symbol(std::string&& s) {add(make_symbol(std::move(s)));}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a property to tne node sequence; i.e. a property-symbol
|
||||||
|
* (starting with ':') and some node
|
||||||
|
*
|
||||||
|
* @param name name (must start with ":"), and some value or list
|
||||||
|
* @param val
|
||||||
|
*/
|
||||||
|
template<typename T> void add_prop(std::string&& name, T&& val) {
|
||||||
|
|
||||||
|
if (name.empty() || name[0] != ':')
|
||||||
|
throw Error{Error::Code::InvalidArgument,
|
||||||
|
"property names must start with ':'"};
|
||||||
|
|
||||||
|
add(make_symbol(std::move(name)));
|
||||||
|
add(std::move(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// deliberately limited stl-like
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this an empty sequence?
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
bool empty() const {return nodes_.empty();}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of elsements in the sequence
|
||||||
|
*
|
||||||
|
* @return number of elmement.
|
||||||
|
*/
|
||||||
|
size_t size() const {return nodes_.size();}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get begin iterator of the sequence
|
||||||
|
*
|
||||||
|
* @return iterator
|
||||||
|
*/
|
||||||
|
const auto begin() const { return nodes_.begin(); }
|
||||||
|
/**
|
||||||
|
* Get the end iterator of the sequnce
|
||||||
|
*
|
||||||
|
* @return an iterator
|
||||||
|
*/
|
||||||
|
const auto end() const { return nodes_.end(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a const ref to the item at idx.
|
||||||
|
*
|
||||||
|
* @param idx index, must be < size()
|
||||||
|
*
|
||||||
|
* @return const ref to the item.
|
||||||
|
*/
|
||||||
|
const auto at(size_t idx) const { return nodes_.at(idx);}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Node> nodes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a list node from a sequence
|
||||||
|
*
|
||||||
|
* @param seq a sequence of nodes
|
||||||
|
*
|
||||||
|
* @return a node
|
||||||
|
*/
|
||||||
|
static Node make_list (Seq&& seq) { return Node{std::move(seq)}; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Sexp::Node to its string representation
|
||||||
|
*
|
||||||
|
* @return the string representation
|
||||||
|
*/
|
||||||
|
std::string to_string() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the type of this Node.
|
||||||
|
*
|
||||||
|
* @return the type
|
||||||
|
*/
|
||||||
|
Type type() const { return type_; }
|
||||||
|
|
||||||
|
/// Some type helpers
|
||||||
|
bool is_list() const { return type() == Type::List; };
|
||||||
|
bool is_string() const { return type() == Type::String; }
|
||||||
|
bool is_number() const { return type() == Type::Number; }
|
||||||
|
bool is_symbol() const { return type() == Type::Symbol; }
|
||||||
|
bool is_nil() const { return is_symbol() && value() == "nil"; }
|
||||||
|
bool is_t() const { return is_symbol() && value() == "t"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The elements of this node; invalid unless this is a list node.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
const Seq& elements() const {
|
||||||
|
if (type() != Type::List)
|
||||||
|
throw Error(Error::Code::InvalidArgument, "no elements for non-list");
|
||||||
|
return seq_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of this node; invalid for list nodes.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
const std::string& value() const {
|
||||||
|
if (type_ == Type::List)
|
||||||
|
throw Error(Error::Code::InvalidArgument, "no value for list");
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Construct a new non-list node
|
||||||
|
*
|
||||||
|
* @param typearg the type of node
|
||||||
|
* @param valuearg the value
|
||||||
|
*/
|
||||||
|
Node(Type typearg, std::string&& valuearg):
|
||||||
|
type_{typearg}, value_{std::move(valuearg)} {
|
||||||
|
if (typearg == Type::List)
|
||||||
|
throw Error(Error::Code::Parsing,
|
||||||
|
"atomic type cannot be a <list>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a list node
|
||||||
|
*
|
||||||
|
* @param kids the list's children
|
||||||
|
*/
|
||||||
|
explicit Node(Seq&& seq):
|
||||||
|
type_{Type::List}, seq_{std::move(seq)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
const Type type_; /**< Type of node */
|
||||||
|
const std::string value_; /**< String value of node (only for
|
||||||
|
* non-Type::Lst)*/
|
||||||
|
const Seq seq_; /**< Children of node (only for
|
||||||
|
* Type::Lst) */
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline std::ostream&
|
||||||
|
operator<<(std::ostream& os, Sexp::Node::Type id)
|
||||||
|
{
|
||||||
|
switch (id) {
|
||||||
|
case Sexp::Node::Type::List: os << "<list>"; break;
|
||||||
|
case Sexp::Node::Type::String: os << "<string>"; break;
|
||||||
|
case Sexp::Node::Type::Number: os << "<number>"; break;
|
||||||
|
case Sexp::Node::Type::Symbol: os << "<symbol>"; break;
|
||||||
|
default: throw std::runtime_error ("unknown node type");
|
||||||
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::ostream&
|
||||||
|
operator<<(std::ostream& os, const Sexp::Node& node)
|
||||||
|
{
|
||||||
|
os << node.to_string();
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Sexp
|
||||||
|
|
||||||
|
|
||||||
|
} // Mu
|
||||||
|
|
||||||
|
#endif /* MU_SEXP_HH__ */
|
||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** Copyright (C) 2017 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
**
|
**
|
||||||
** This library is free software; you can redistribute it and/or
|
** This library is free software; you can redistribute it and/or
|
||||||
** modify it under the terms of the GNU Lesser General Public License
|
** modify it under the terms of the GNU Lesser General Public License
|
||||||
@ -31,23 +31,23 @@ using namespace Mu;
|
|||||||
static void
|
static void
|
||||||
test_param_getters()
|
test_param_getters()
|
||||||
{
|
{
|
||||||
const auto node { Sexp::parse(R"((foo :bar 123 :cuux "456" :boo nil :bah true))")};
|
const auto node { Sexp::Node::make(R"((foo :bar 123 :cuux "456" :boo nil :bah true))")};
|
||||||
|
|
||||||
std::cout << node << "\n";
|
std::cout << node << "\n";
|
||||||
|
|
||||||
g_assert_cmpint(Command::get_int_or(node.children,"bar"), ==, 123);
|
g_assert_cmpint(Command::get_int_or(node.elements(), "bar"), ==, 123);
|
||||||
assert_equal(Command::get_string_or(node.children, "bra", "bla"), "bla");
|
assert_equal(Command::get_string_or(node.elements(), "bra", "bla"), "bla");
|
||||||
assert_equal(Command::get_string_or(node.children, "cuux"), "456");
|
assert_equal(Command::get_string_or(node.elements(), "cuux"), "456");
|
||||||
|
|
||||||
g_assert_true(Command::get_bool_or(node.children,"boo") == false);
|
g_assert_true(Command::get_bool_or(node.elements(),"boo") == false);
|
||||||
g_assert_true(Command::get_bool_or(node.children,"bah") == true);
|
g_assert_true(Command::get_bool_or(node.elements(),"bah") == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
call (const Command::CommandMap& cmap, const std::string& sexp) try
|
call (const Command::CommandMap& cmap, const std::string& sexp) try
|
||||||
{
|
{
|
||||||
const auto node{Sexp::parse(sexp)};
|
const auto node{Sexp::Node::make(sexp)};
|
||||||
g_message ("invoking %s", to_string(node).c_str());
|
g_message ("invoking %s", to_string(node).c_str());
|
||||||
|
|
||||||
invoke (cmap, node);
|
invoke (cmap, node);
|
||||||
@ -70,8 +70,8 @@ test_command()
|
|||||||
|
|
||||||
cmap.emplace("my-command",
|
cmap.emplace("my-command",
|
||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{ {"param1", ArgInfo{Sexp::Type::String, true, "some string" }},
|
ArgMap{ {"param1", ArgInfo{Sexp::Node::Type::String, true, "some string" }},
|
||||||
{"param2", ArgInfo{Sexp::Type::Integer, false, "some integer"}}},
|
{"param2", ArgInfo{Sexp::Node::Type::Number, false, "some integer"}}},
|
||||||
"My command,",
|
"My command,",
|
||||||
{}});
|
{}});
|
||||||
|
|
||||||
@ -93,8 +93,8 @@ test_command2()
|
|||||||
cmap.emplace("bla",
|
cmap.emplace("bla",
|
||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{
|
ArgMap{
|
||||||
{"foo", ArgInfo{Sexp::Type::Integer, false, "foo"}},
|
{"foo", ArgInfo{Sexp::Node::Type::Number, false, "foo"}},
|
||||||
{"bar", ArgInfo{Sexp::Type::String, false, "bar"}},
|
{"bar", ArgInfo{Sexp::Node::Type::String, false, "bar"}},
|
||||||
},"yeah",
|
},"yeah",
|
||||||
[&](const auto& params){}});
|
[&](const auto& params){}});
|
||||||
|
|
||||||
@ -115,8 +115,8 @@ test_command_fail()
|
|||||||
|
|
||||||
cmap.emplace("my-command",
|
cmap.emplace("my-command",
|
||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{ {"param1", ArgInfo{Sexp::Type::String, true, "some string" }},
|
ArgMap{ {"param1", ArgInfo{Sexp::Node::Type::String, true, "some string" }},
|
||||||
{"param2", ArgInfo{Sexp::Type::Integer, false, "some integer"}}},
|
{"param2", ArgInfo{Sexp::Node::Type::Number, false, "some integer"}}},
|
||||||
"My command,",
|
"My command,",
|
||||||
{}});
|
{}});
|
||||||
|
|
||||||
@ -124,7 +124,6 @@ test_command_fail()
|
|||||||
g_assert_false (call(cmap, "(my-command2)"));
|
g_assert_false (call(cmap, "(my-command2)"));
|
||||||
g_assert_false(call(cmap, "(my-command :param1 123 :param2 123)"));
|
g_assert_false(call(cmap, "(my-command :param1 123 :param2 123)"));
|
||||||
g_assert_false(call(cmap, "(my-command :param1 \"hello\" :param2 \"123\")"));
|
g_assert_false(call(cmap, "(my-command :param1 \"hello\" :param2 \"123\")"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
|
||||||
**
|
|
||||||
** This library is free software; you can redistribute it and/or
|
|
||||||
** modify it under the terms of the GNU Lesser General Public License
|
|
||||||
** as published by the Free Software Foundation; either version 2.1
|
|
||||||
** of the License, or (at your option) any later version.
|
|
||||||
**
|
|
||||||
** This library is distributed in the hope that it will be useful,
|
|
||||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
** Lesser General Public License for more details.
|
|
||||||
**
|
|
||||||
** You should have received a copy of the GNU Lesser General Public
|
|
||||||
** License along with this library; if not, write to the Free
|
|
||||||
** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
|
|
||||||
** 02110-1301, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "mu-command-parser.hh"
|
|
||||||
#include "mu-utils.hh"
|
|
||||||
|
|
||||||
using namespace Mu;
|
|
||||||
|
|
||||||
static bool
|
|
||||||
check_parse (const std::string& expr, const std::string& expected)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
const auto parsed{to_string(Sexp::parse(expr))};
|
|
||||||
g_assert_cmpstr(parsed.c_str(), ==, expected.c_str());
|
|
||||||
return true;
|
|
||||||
|
|
||||||
} catch (const Error& err) {
|
|
||||||
g_warning ("caught exception parsing '%s': %s", expr.c_str(), err.what());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_parser()
|
|
||||||
{
|
|
||||||
check_parse(R"(:foo-123)", "<symbol>{:foo-123}");
|
|
||||||
check_parse(R"("foo")", "<string>{foo}");
|
|
||||||
check_parse(R"(12345)", "<integer>{12345}");
|
|
||||||
check_parse(R"(-12345)", "<integer>{-12345}");
|
|
||||||
check_parse(R"((123 bar "cuux"))", "<list>(<integer>{123}<symbol>{bar}<string>{cuux})");
|
|
||||||
|
|
||||||
check_parse(R"("\"")", "<string>{\"}");
|
|
||||||
check_parse(R"("\\")", "<string>{\\}");
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main (int argc, char *argv[]) try
|
|
||||||
{
|
|
||||||
g_test_init (&argc, &argv, NULL);
|
|
||||||
|
|
||||||
if (argc == 2) {
|
|
||||||
std::cout << Sexp::parse(argv[1]) << '\n';
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_test_add_func ("/utils/command-parser/parse", test_parser);
|
|
||||||
|
|
||||||
return g_test_run ();
|
|
||||||
|
|
||||||
|
|
||||||
} catch (const std::runtime_error& re) {
|
|
||||||
std::cerr << re.what() << "\n";
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
133
lib/utils/test-sexp.cc
Normal file
133
lib/utils/test-sexp.cc
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
|
**
|
||||||
|
** This library is free software; you can redistribute it and/or
|
||||||
|
** modify it under the terms of the GNU Lesser General Public License
|
||||||
|
** as published by the Free Software Foundation; either version 2.1
|
||||||
|
** of the License, or (at your option) any later version.
|
||||||
|
**
|
||||||
|
** This library is distributed in the hope that it will be useful,
|
||||||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
** Lesser General Public License for more details.
|
||||||
|
**
|
||||||
|
** You should have received a copy of the GNU Lesser General Public
|
||||||
|
** License along with this library; if not, write to the Free
|
||||||
|
** Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
** 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "mu-command-parser.hh"
|
||||||
|
#include "mu-utils.hh"
|
||||||
|
|
||||||
|
using namespace Mu;
|
||||||
|
using namespace Sexp;
|
||||||
|
|
||||||
|
static bool
|
||||||
|
check_parse (const std::string& expr, const std::string& expected)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const auto parsed{to_string(Node::make(expr))};
|
||||||
|
g_assert_cmpstr(parsed.c_str(), ==, expected.c_str());
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const Error& err) {
|
||||||
|
g_warning ("caught exception parsing '%s': %s", expr.c_str(), err.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_parser()
|
||||||
|
{
|
||||||
|
check_parse(":foo-123", ":foo-123");
|
||||||
|
check_parse("foo", "foo");
|
||||||
|
check_parse(R"(12345)", "12345");
|
||||||
|
check_parse(R"(-12345)", "-12345");
|
||||||
|
check_parse(R"((123 bar "cuux"))", "(123 bar \"cuux\")");
|
||||||
|
|
||||||
|
check_parse(R"("foo\"bar\"cuux")", "\"foo\\\"bar\\\"cuux\"");
|
||||||
|
|
||||||
|
check_parse(R"("foo
|
||||||
|
bar")", "\"foo\\nbar\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_builder()
|
||||||
|
{
|
||||||
|
const auto nstr{Node::make_string("foo")};
|
||||||
|
g_assert_true(nstr.value() == "foo");
|
||||||
|
g_assert_true(nstr.type() == Node::Type::String);
|
||||||
|
assert_equal(nstr.to_string(), "\"foo\"");
|
||||||
|
|
||||||
|
const auto nnum{Node::make_number(123)};
|
||||||
|
g_assert_true(nnum.value() == "123");
|
||||||
|
g_assert_true(nnum.type() == Node::Type::Number);
|
||||||
|
assert_equal(nnum.to_string(), "123");
|
||||||
|
|
||||||
|
const auto nsym{Node::make_symbol("blub")};
|
||||||
|
g_assert_true(nsym.value() == "blub");
|
||||||
|
g_assert_true(nsym.type() == Node::Type::Symbol);
|
||||||
|
assert_equal(nsym.to_string(), "blub");
|
||||||
|
|
||||||
|
Node::Seq seq;
|
||||||
|
seq.add(Node::make_string("foo"));
|
||||||
|
seq.add(Node::make_number(123));
|
||||||
|
seq.add(Node::make_symbol("blub"));
|
||||||
|
|
||||||
|
const auto nlst = Node::make_list(std::move(seq));
|
||||||
|
g_assert_true(nlst.elements().size() == 3);
|
||||||
|
g_assert_true(nlst.type() == Node::Type::List);
|
||||||
|
g_assert_true(nlst.elements().at(1).value() == "123");
|
||||||
|
|
||||||
|
assert_equal(nlst.to_string(),"(\"foo\" 123 blub)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_props()
|
||||||
|
{
|
||||||
|
Node::Seq seq;
|
||||||
|
|
||||||
|
seq.add_prop(":foo", "bär");
|
||||||
|
seq.add_prop(":cuux", 123);
|
||||||
|
seq.add_prop(":flub", Node::make_symbol("fnord"));
|
||||||
|
|
||||||
|
Node::Seq seq2;
|
||||||
|
seq2.add(Node::make_string("foo"));
|
||||||
|
seq2.add(Node::make_number(123));
|
||||||
|
seq2.add(Node::make_symbol("blub"));
|
||||||
|
|
||||||
|
seq.add_prop(":boo", std::move(seq2));
|
||||||
|
|
||||||
|
Node expr = Node::make_list(std::move(seq));
|
||||||
|
assert_equal(expr.to_string(),
|
||||||
|
"(:foo \"b\\303\\244r\" :cuux 123 :flub fnord :boo (\"foo\" 123 blub))");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[]) try
|
||||||
|
{
|
||||||
|
g_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
std::cout << Sexp::Node::make(argv[1]) << '\n';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_test_add_func ("/utils/sexp/parser", test_parser);
|
||||||
|
g_test_add_func ("/utils/sexp/builder", test_builder);
|
||||||
|
g_test_add_func ("/utils/sexp/props", test_props);
|
||||||
|
|
||||||
|
return g_test_run ();
|
||||||
|
|
||||||
|
|
||||||
|
} catch (const std::runtime_error& re) {
|
||||||
|
std::cerr << re.what() << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@ -146,6 +146,20 @@ print_expr (const char* frm, ...)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_expr (const Node& sexp)
|
||||||
|
{
|
||||||
|
print_expr ("%s", sexp.to_string().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_expr (Node::Seq&& seq)
|
||||||
|
{
|
||||||
|
print_expr (Node::make_list(std::move(seq)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
G_GNUC_PRINTF(2,3) static MuError
|
G_GNUC_PRINTF(2,3) static MuError
|
||||||
print_error (MuError errcode, const char* frm, ...)
|
print_error (MuError errcode, const char* frm, ...)
|
||||||
{
|
{
|
||||||
@ -156,7 +170,12 @@ print_error (MuError errcode, const char* frm, ...)
|
|||||||
g_vasprintf (&msg, frm, ap);
|
g_vasprintf (&msg, frm, ap);
|
||||||
va_end (ap);
|
va_end (ap);
|
||||||
|
|
||||||
print_expr ("(:error %u :message %s)", errcode, quote(msg).c_str());
|
Node::Seq err_sexp;
|
||||||
|
err_sexp.add_prop(":error", (int)errcode);
|
||||||
|
err_sexp.add_prop(":message", msg);
|
||||||
|
|
||||||
|
print_expr(Node::make_list(std::move(err_sexp)));
|
||||||
|
|
||||||
g_free (msg);
|
g_free (msg);
|
||||||
|
|
||||||
return errcode;
|
return errcode;
|
||||||
@ -289,6 +308,7 @@ add_handler (Context& context, const Parameters& params)
|
|||||||
|
|
||||||
auto sexp{mu_msg_to_sexp (msg, docid, NULL, MU_MSG_OPTION_VERIFY)};
|
auto sexp{mu_msg_to_sexp (msg, docid, NULL, MU_MSG_OPTION_VERIFY)};
|
||||||
print_expr ("(:update %s :move nil)", sexp);
|
print_expr ("(:update %s :move nil)", sexp);
|
||||||
|
|
||||||
mu_msg_unref(msg);
|
mu_msg_unref(msg);
|
||||||
g_free (sexp);
|
g_free (sexp);
|
||||||
}
|
}
|
||||||
@ -322,7 +342,6 @@ each_part (MuMsg *msg, MuMsgPart *part, PartInfo *pinfo)
|
|||||||
pinfo->attlist = g_slist_append (pinfo->attlist, att);
|
pinfo->attlist = g_slist_append (pinfo->attlist, att);
|
||||||
|
|
||||||
g_free (cachefile);
|
g_free (cachefile);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -410,6 +429,7 @@ compose_handler (Context& context, const Parameters& params)
|
|||||||
atts = (ctype == FORWARD) ? include_attachments (msg, opts) : NULL;
|
atts = (ctype == FORWARD) ? include_attachments (msg, opts) : NULL;
|
||||||
mu_msg_unref (msg);
|
mu_msg_unref (msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
print_expr ("(:compose %s :original %s :include %s)",
|
print_expr ("(:compose %s :original %s :include %s)",
|
||||||
typestr.c_str(), sexp ? sexp : "nil", atts ? atts : "nil");
|
typestr.c_str(), sexp ? sexp : "nil", atts ? atts : "nil");
|
||||||
|
|
||||||
@ -419,7 +439,7 @@ compose_handler (Context& context, const Parameters& params)
|
|||||||
|
|
||||||
|
|
||||||
struct SexpData {
|
struct SexpData {
|
||||||
GString *gstr;
|
Sexp::Node::Seq contacts;
|
||||||
gboolean personal;
|
gboolean personal;
|
||||||
time_t last_seen;
|
time_t last_seen;
|
||||||
gint64 tstamp;
|
gint64 tstamp;
|
||||||
@ -452,8 +472,11 @@ each_contact_sexp (const char* full_address,
|
|||||||
if (!email || !strstr (email, "@"))
|
if (!email || !strstr (email, "@"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
g_string_append_printf (sdata->gstr, "(%s . %zu)\n",
|
Node::Seq contact;
|
||||||
quote(full_address).c_str(), sdata->rank);
|
contact.add_prop(":address", full_address);
|
||||||
|
contact.add_prop(":rank", sdata->rank);
|
||||||
|
|
||||||
|
sdata->contacts.add(Node::make_list(std::move(contact)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -465,33 +488,25 @@ each_contact_sexp (const char* full_address,
|
|||||||
*
|
*
|
||||||
* @return the sexp
|
* @return the sexp
|
||||||
*/
|
*/
|
||||||
static char*
|
static Sexp::Node
|
||||||
contacts_to_sexp (const MuContacts *contacts, bool personal,
|
contacts_to_sexp (const MuContacts *contacts, bool personal,
|
||||||
int64_t last_seen, gint64 tstamp)
|
int64_t last_seen, gint64 tstamp)
|
||||||
{
|
{
|
||||||
|
|
||||||
g_return_val_if_fail (contacts, NULL);
|
|
||||||
|
|
||||||
SexpData sdata{};
|
SexpData sdata{};
|
||||||
sdata.personal = personal;
|
sdata.personal = personal;
|
||||||
sdata.last_seen = last_seen;
|
sdata.last_seen = last_seen;
|
||||||
sdata.tstamp = tstamp;
|
sdata.tstamp = tstamp;
|
||||||
sdata.rank = 0;
|
sdata.rank = 0;
|
||||||
|
|
||||||
/* make a guess for the initial size */
|
|
||||||
sdata.gstr = g_string_sized_new (mu_contacts_count(contacts) * 128);
|
|
||||||
g_string_append (sdata.gstr, "(:contacts (");
|
|
||||||
|
|
||||||
const auto cutoff{g_get_monotonic_time()};
|
const auto cutoff{g_get_monotonic_time()};
|
||||||
mu_contacts_foreach (contacts, (MuContactsForeachFunc)each_contact_sexp, &sdata);
|
mu_contacts_foreach (contacts, (MuContactsForeachFunc)each_contact_sexp, &sdata);
|
||||||
/* pass a string, elisp doesn't like 64-bit nums */
|
Node::Seq seq;
|
||||||
g_string_append_printf (sdata.gstr,
|
seq.add_prop(":contacts", std::move(sdata.contacts));
|
||||||
") :tstamp \"%" G_GINT64_FORMAT "\")", cutoff);
|
seq.add_prop(":tstamp", Node::make_string(format("%" G_GINT64_FORMAT, cutoff)));
|
||||||
|
|
||||||
return g_string_free (sdata.gstr, FALSE);
|
return Node::make_list(std::move(seq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
contacts_handler (Context& context, const Parameters& params)
|
contacts_handler (Context& context, const Parameters& params)
|
||||||
{
|
{
|
||||||
@ -508,9 +523,7 @@ contacts_handler (Context& context, const Parameters& params)
|
|||||||
throw Error{Error::Code::Internal, "failed to get contacts"};
|
throw Error{Error::Code::Internal, "failed to get contacts"};
|
||||||
|
|
||||||
/* dump the contacts cache as a giant sexp */
|
/* dump the contacts cache as a giant sexp */
|
||||||
auto sexp = contacts_to_sexp (contacts, personal, after, tstamp);
|
print_expr(contacts_to_sexp (contacts, personal, after, tstamp));
|
||||||
print_expr ("%s\n", sexp);
|
|
||||||
g_free (sexp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -526,7 +539,11 @@ save_part (MuMsg *msg, unsigned docid, unsigned index,
|
|||||||
path.c_str(), index, &gerr))
|
path.c_str(), index, &gerr))
|
||||||
throw Error{Error::Code::File, &gerr, "failed to save part"};
|
throw Error{Error::Code::File, &gerr, "failed to save part"};
|
||||||
|
|
||||||
print_expr ("(:info save :message %s)", quote(path + " has been saved").c_str());
|
Node::Seq seq;
|
||||||
|
seq.add_prop(":info", Node::make_symbol("save"));
|
||||||
|
seq.add_prop(":message", Node::make_string(format("%s has been saved", path.c_str())));
|
||||||
|
|
||||||
|
print_expr(std::move(seq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -550,9 +567,12 @@ open_part (MuMsg *msg, unsigned docid, unsigned index, MuMsgOptions opts)
|
|||||||
throw Error{Error::Code::File, &gerr, "failed to play"};
|
throw Error{Error::Code::File, &gerr, "failed to play"};
|
||||||
}
|
}
|
||||||
|
|
||||||
print_expr ("(:info open :message %s)",
|
Node::Seq seq;
|
||||||
quote(std::string{targetpath} + " has been opened").c_str());
|
seq.add_prop(":info", Node::make_symbol("open"));
|
||||||
|
seq.add_prop(":message", Node::make_string(format("%s has been opened", targetpath)));
|
||||||
g_free (targetpath);
|
g_free (targetpath);
|
||||||
|
|
||||||
|
print_expr(std::move(seq));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -576,19 +596,16 @@ temp_part (MuMsg *msg, unsigned docid, unsigned index,
|
|||||||
throw Error{Error::Code::File, &gerr, "saving failed"};
|
throw Error{Error::Code::File, &gerr, "saving failed"};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto qpath{quote(path)};
|
Node::Seq seq;
|
||||||
g_free(path);
|
seq.add_prop(":temp", path);
|
||||||
|
seq.add_prop(":what", std::string(what));
|
||||||
|
seq.add_prop(":docid", docid);
|
||||||
|
|
||||||
if (!param.empty())
|
if (!param.empty())
|
||||||
print_expr ("(:temp %s"
|
seq.add_prop(":param", std::string(param));
|
||||||
" :what \"%s\""
|
|
||||||
" :docid %u"
|
g_free(path);
|
||||||
" :param %s"
|
print_expr(std::move(seq));
|
||||||
")",
|
|
||||||
qpath.c_str(), what.c_str(), docid, quote(param).c_str());
|
|
||||||
else
|
|
||||||
print_expr ("(:temp %s :what \"%s\" :docid %u)",
|
|
||||||
qpath.c_str(), what.c_str(), docid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -740,9 +757,19 @@ find_handler (Context& context, const Parameters& params)
|
|||||||
/* before sending new results, send an 'erase' message, so the frontend
|
/* before sending new results, send an 'erase' message, so the frontend
|
||||||
* knows it should erase the headers buffer. this will ensure that the
|
* knows it should erase the headers buffer. this will ensure that the
|
||||||
* output of two finds will not be mixed. */
|
* output of two finds will not be mixed. */
|
||||||
print_expr ("(:erase t)");
|
{
|
||||||
|
Node::Seq seq;
|
||||||
|
seq.add_prop(":erase", Node::make_symbol("t"));
|
||||||
|
print_expr(std::move(seq));
|
||||||
|
}
|
||||||
|
//print_expr ("(:erase t)");
|
||||||
|
{
|
||||||
const auto foundnum{print_sexps (miter, maxnum)};
|
const auto foundnum{print_sexps (miter, maxnum)};
|
||||||
print_expr ("(:found %u)", foundnum);
|
Node::Seq seq;
|
||||||
|
seq.add_prop(":found", foundnum);
|
||||||
|
print_expr(std::move(seq));
|
||||||
|
}
|
||||||
|
//print_expr ("(:found %u)", foundnum);
|
||||||
mu_msg_iter_destroy (miter);
|
mu_msg_iter_destroy (miter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -802,9 +829,13 @@ index_msg_cb (MuIndexStats *stats, void *user_data)
|
|||||||
if (stats->_processed % 1000)
|
if (stats->_processed % 1000)
|
||||||
return MU_OK;
|
return MU_OK;
|
||||||
|
|
||||||
print_expr ("(:info index :status running "
|
Node::Seq seq;
|
||||||
":processed %u :updated %u)",
|
seq.add_prop(":info", Node::make_symbol("index"));
|
||||||
stats->_processed, stats->_updated);
|
seq.add_prop(":status", Node::make_symbol("running"));
|
||||||
|
seq.add_prop(":processed", stats->_processed);
|
||||||
|
seq.add_prop(":updated", stats->_updated);
|
||||||
|
|
||||||
|
print_expr(std::move(seq));
|
||||||
|
|
||||||
return MU_OK;
|
return MU_OK;
|
||||||
}
|
}
|
||||||
@ -828,11 +859,16 @@ index_and_maybe_cleanup (MuIndex *index, bool cleanup, bool lazy_check)
|
|||||||
throw Error{Error::Code::Store, &gerr, "cleanup failed"};
|
throw Error{Error::Code::Store, &gerr, "cleanup failed"};
|
||||||
}
|
}
|
||||||
|
|
||||||
print_expr ("(:info index :status complete "
|
Node::Seq seq;
|
||||||
":processed %u :updated %u :cleaned-up %u)",
|
seq.add_prop(":info", Node::make_symbol("index"));
|
||||||
stats._processed, stats._updated, stats2._cleaned_up);
|
seq.add_prop(":status", Node::make_symbol("complete"));
|
||||||
|
seq.add_prop(":processed", stats._processed);
|
||||||
|
seq.add_prop(":updated", stats._updated);
|
||||||
|
seq.add_prop(":cleaned-up", stats2._cleaned_up);
|
||||||
|
|
||||||
return rv;
|
print_expr(std::move(seq));
|
||||||
|
|
||||||
|
return MU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -865,7 +901,11 @@ mkdir_handler (Context& context, const Parameters& params)
|
|||||||
if (!mu_maildir_mkdir(path.c_str(), 0755, FALSE, &gerr))
|
if (!mu_maildir_mkdir(path.c_str(), 0755, FALSE, &gerr))
|
||||||
throw Error{Error::Code::File, &gerr, "failed to create maildir"};
|
throw Error{Error::Code::File, &gerr, "failed to create maildir"};
|
||||||
|
|
||||||
print_expr ("(:info mkdir :message \"%s has been created\")", path.c_str());
|
Node::Seq seq;
|
||||||
|
seq.add_prop(":info", "mkdir");
|
||||||
|
seq.add_prop(":message", format("%s has been created", path.c_str()));
|
||||||
|
|
||||||
|
print_expr(std::move(seq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -996,7 +1036,7 @@ move_handler (Context& context, const Parameters& params)
|
|||||||
|
|
||||||
if (flags == MU_FLAG_INVALID) {
|
if (flags == MU_FLAG_INVALID) {
|
||||||
mu_msg_unref(msg);
|
mu_msg_unref(msg);
|
||||||
throw Error{Error::Code::InvalidArgument, "invalid flagse"};
|
throw Error{Error::Code::InvalidArgument, "invalid flags"};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1018,46 +1058,43 @@ ping_handler (Context& context, const Parameters& params)
|
|||||||
throw Error{Error::Code::Store, &gerr, "failed to read store"};
|
throw Error{Error::Code::Store, &gerr, "failed to read store"};
|
||||||
|
|
||||||
const auto queries = get_string_vec (params, "queries");
|
const auto queries = get_string_vec (params, "queries");
|
||||||
const auto qresults = [&]() -> std::string {
|
Node::Seq qresults;
|
||||||
if (queries.empty())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
std::string res{":queries ("};
|
|
||||||
for (auto&& q: queries) {
|
for (auto&& q: queries) {
|
||||||
const auto count{mu_query_count_run (context.query, q.c_str())};
|
const auto count{mu_query_count_run (context.query, q.c_str())};
|
||||||
const auto unreadq{format("flag:unread AND (%s)", q.c_str())};
|
const auto unreadq{format("flag:unread AND (%s)", q.c_str())};
|
||||||
const auto unread{mu_query_count_run (context.query, unreadq.c_str())};
|
const auto unread{mu_query_count_run (context.query, unreadq.c_str())};
|
||||||
res += format("(:query %s :count %zu :unread %zu)", quote(q).c_str(),
|
|
||||||
count, unread);
|
|
||||||
}
|
|
||||||
return res + ")";
|
|
||||||
}();
|
|
||||||
|
|
||||||
const auto personal = [&]() ->std::string {
|
Node::Seq seq;
|
||||||
auto addrs{mu_store_personal_addresses (context.store)};
|
seq.add_prop(":query", std::string(q));
|
||||||
std::string res;
|
seq.add_prop(":count", count);
|
||||||
if (addrs && g_strv_length(addrs) != 0) {
|
seq.add_prop(":unread", unread);
|
||||||
res = ":personal-addresses (";
|
|
||||||
for (int i = 0; addrs[i]; ++i)
|
|
||||||
res += quote(addrs[i]) + ' ';
|
|
||||||
res += ")";
|
|
||||||
}
|
|
||||||
g_strfreev(addrs);
|
|
||||||
return res;
|
|
||||||
}();
|
|
||||||
|
|
||||||
print_expr ("(:pong \"mu\" :props ("
|
qresults.add(Node::make_list(std::move(seq)));
|
||||||
":version \"" VERSION "\" "
|
}
|
||||||
"%s "
|
|
||||||
":database-path %s "
|
Node::Seq addrs;
|
||||||
":root-maildir %s "
|
char **addrs_strv{mu_store_personal_addresses (context.store)};
|
||||||
":doccount %u "
|
for (auto cur = addrs_strv; cur && *cur; ++cur)
|
||||||
"%s))",
|
addrs.add(*cur);
|
||||||
personal.c_str(),
|
g_strfreev (addrs_strv);
|
||||||
quote(mu_store_database_path(context.store)).c_str(),
|
|
||||||
quote(mu_store_root_maildir(context.store)).c_str(),
|
const auto dbpath{mu_store_database_path(context.store)};
|
||||||
storecount,
|
const auto root{mu_store_root_maildir(context.store)};
|
||||||
qresults.c_str());
|
|
||||||
|
Node::Seq seq;
|
||||||
|
seq.add_prop(":pong", "mu");
|
||||||
|
|
||||||
|
Node::Seq propseq;
|
||||||
|
propseq.add_prop(":version", VERSION);
|
||||||
|
propseq.add_prop(":personal-addresses", std::move(addrs));
|
||||||
|
propseq.add_prop(":database-path", dbpath);
|
||||||
|
propseq.add_prop(":root-maildir", root);
|
||||||
|
propseq.add_prop(":doccount", storecount);
|
||||||
|
propseq.add_prop(":queries", std::move(qresults));
|
||||||
|
|
||||||
|
seq.add_prop(":props", std::move(propseq));
|
||||||
|
|
||||||
|
print_expr(std::move(seq));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -1081,8 +1118,10 @@ remove_handler (Context& context, const Parameters& params)
|
|||||||
throw Error(Error::Code::Store,
|
throw Error(Error::Code::Store,
|
||||||
"failed to remove message @ %s (%d) from store",
|
"failed to remove message @ %s (%d) from store",
|
||||||
path.c_str(), docid);
|
path.c_str(), docid);
|
||||||
|
Node::Seq seq;
|
||||||
|
seq.add_prop(":remove", docid);
|
||||||
|
|
||||||
print_expr ("(:remove %u)", docid);
|
print_expr(std::move(seq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1095,7 +1134,12 @@ sent_handler (Context& context, const Parameters& params)
|
|||||||
if (docid == MU_STORE_INVALID_DOCID)
|
if (docid == MU_STORE_INVALID_DOCID)
|
||||||
throw Error{Error::Code::Store, &gerr, "failed to add path"};
|
throw Error{Error::Code::Store, &gerr, "failed to add path"};
|
||||||
|
|
||||||
print_expr ("(:sent t :path %s :docid %u)", quote(path).c_str(), docid);
|
Node::Seq seq;
|
||||||
|
seq.add_prop (":sent", Node::make_symbol("t"));
|
||||||
|
seq.add_prop (":path", std::string(path));
|
||||||
|
seq.add_prop (":docid", docid);
|
||||||
|
|
||||||
|
print_expr (std::move(seq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1118,11 +1162,11 @@ view_handler (Context& context, const Parameters& params)
|
|||||||
if (!msg)
|
if (!msg)
|
||||||
throw Error{Error::Code::Store, &gerr, "failed to find message for view"};
|
throw Error{Error::Code::Store, &gerr, "failed to find message for view"};
|
||||||
|
|
||||||
auto sexp{mu_msg_to_sexp(msg, docid, {}, message_options(params))};
|
Node::Seq seq;
|
||||||
|
seq.add_prop(":view", msg_to_sexp(msg, docid, {}, message_options(params)));
|
||||||
mu_msg_unref(msg);
|
mu_msg_unref(msg);
|
||||||
|
|
||||||
print_expr ("(:view %s)\n", sexp);
|
print_expr (std::move(seq));
|
||||||
g_free (sexp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1131,6 +1175,8 @@ make_command_map (Context& context)
|
|||||||
{
|
{
|
||||||
CommandMap cmap;
|
CommandMap cmap;
|
||||||
|
|
||||||
|
using Type = Node::Type;
|
||||||
|
|
||||||
cmap.emplace("add",
|
cmap.emplace("add",
|
||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{ {"path", ArgInfo{Type::String, true, "file system path to the message" }}},
|
ArgMap{ {"path", ArgInfo{Type::String, true, "file system path to the message" }}},
|
||||||
@ -1141,7 +1187,7 @@ make_command_map (Context& context)
|
|||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{{"type", ArgInfo{Type::Symbol, true,
|
ArgMap{{"type", ArgInfo{Type::Symbol, true,
|
||||||
"type of composition: reply/forward/edit/resend/new"}},
|
"type of composition: reply/forward/edit/resend/new"}},
|
||||||
{"docid", ArgInfo{Type::Integer, false,"document id of parent-message, if any"}},
|
{"docid", ArgInfo{Type::Number, false,"document id of parent-message, if any"}},
|
||||||
{"decrypt", ArgInfo{Type::Symbol, false, "whether to decrypt encrypted parts (if any)" }}},
|
{"decrypt", ArgInfo{Type::Symbol, false, "whether to decrypt encrypted parts (if any)" }}},
|
||||||
"get contact information",
|
"get contact information",
|
||||||
[&](const auto& params){compose_handler(context, params);}});
|
[&](const auto& params){compose_handler(context, params);}});
|
||||||
@ -1159,8 +1205,8 @@ make_command_map (Context& context)
|
|||||||
|
|
||||||
cmap.emplace("extract",
|
cmap.emplace("extract",
|
||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{{"docid", ArgInfo{Type::Integer, true, "document for the message" }},
|
ArgMap{{"docid", ArgInfo{Type::Number, true, "document for the message" }},
|
||||||
{"index", ArgInfo{Type::Integer, true, "index for the part to operate on" }},
|
{"index", ArgInfo{Type::Number, true, "index for the part to operate on" }},
|
||||||
{"action", ArgInfo{Type::Symbol, true, "what to do with the part" }},
|
{"action", ArgInfo{Type::Symbol, true, "what to do with the part" }},
|
||||||
{"decrypt", ArgInfo{Type::Symbol, false,
|
{"decrypt", ArgInfo{Type::Symbol, false,
|
||||||
"whether to decrypt encrypted parts (if any)" }},
|
"whether to decrypt encrypted parts (if any)" }},
|
||||||
@ -1178,7 +1224,7 @@ make_command_map (Context& context)
|
|||||||
{"sortfield", ArgInfo{Type::Symbol, false, "the field to sort results by" }},
|
{"sortfield", ArgInfo{Type::Symbol, false, "the field to sort results by" }},
|
||||||
{"descending", ArgInfo{Type::Symbol, false,
|
{"descending", ArgInfo{Type::Symbol, false,
|
||||||
"whether to sort in descending order" }},
|
"whether to sort in descending order" }},
|
||||||
{"maxnum", ArgInfo{Type::Integer, false,
|
{"maxnum", ArgInfo{Type::Number, false,
|
||||||
"maximum number of result (hint)" }},
|
"maximum number of result (hint)" }},
|
||||||
{"skip-dups", ArgInfo{Type::Symbol, false,
|
{"skip-dups", ArgInfo{Type::Symbol, false,
|
||||||
"whether to skip messages with duplicate message-ids" }},
|
"whether to skip messages with duplicate message-ids" }},
|
||||||
@ -1207,7 +1253,7 @@ make_command_map (Context& context)
|
|||||||
|
|
||||||
cmap.emplace("move",
|
cmap.emplace("move",
|
||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{{"docid", ArgInfo{Type::Integer, false, "document-id"}},
|
ArgMap{{"docid", ArgInfo{Type::Number, false, "document-id"}},
|
||||||
{"msgid", ArgInfo{Type::String, false, "message-id"}},
|
{"msgid", ArgInfo{Type::String, false, "message-id"}},
|
||||||
{"flags", ArgInfo{Type::String, false, "new flags for the message"}},
|
{"flags", ArgInfo{Type::String, false, "new flags for the message"}},
|
||||||
{"maildir", ArgInfo{Type::String, false, "the target maildir" }},
|
{"maildir", ArgInfo{Type::String, false, "the target maildir" }},
|
||||||
@ -1239,7 +1285,7 @@ make_command_map (Context& context)
|
|||||||
|
|
||||||
cmap.emplace("remove",
|
cmap.emplace("remove",
|
||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{ {"docid", ArgInfo{Type::Integer, true,
|
ArgMap{ {"docid", ArgInfo{Type::Number, true,
|
||||||
"document-id for the message to remove" }}},
|
"document-id for the message to remove" }}},
|
||||||
"remove a message from filesystem and database",
|
"remove a message from filesystem and database",
|
||||||
[&](const auto& params){remove_handler(context, params);}});
|
[&](const auto& params){remove_handler(context, params);}});
|
||||||
@ -1254,7 +1300,7 @@ make_command_map (Context& context)
|
|||||||
|
|
||||||
cmap.emplace("view",
|
cmap.emplace("view",
|
||||||
CommandInfo{
|
CommandInfo{
|
||||||
ArgMap{{"docid", ArgInfo{Type::Integer, false, "document-id"}},
|
ArgMap{{"docid", ArgInfo{Type::Number, false, "document-id"}},
|
||||||
{"msgid", ArgInfo{Type::String, false, "message-id"}},
|
{"msgid", ArgInfo{Type::String, false, "message-id"}},
|
||||||
{"path", ArgInfo{Type::String, false, "message filesystem path"}},
|
{"path", ArgInfo{Type::String, false, "message filesystem path"}},
|
||||||
|
|
||||||
@ -1277,7 +1323,7 @@ mu_cmd_server (MuConfig *opts, GError **err) try
|
|||||||
if (opts->commands) {
|
if (opts->commands) {
|
||||||
Context ctx{};
|
Context ctx{};
|
||||||
auto cmap = make_command_map(ctx);
|
auto cmap = make_command_map(ctx);
|
||||||
invoke(cmap, Sexp::parse("(help :full t)"));
|
invoke(cmap, Sexp::Node::make("(help :full t)"));
|
||||||
return MU_OK;
|
return MU_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1285,7 +1331,7 @@ mu_cmd_server (MuConfig *opts, GError **err) try
|
|||||||
context.command_map = make_command_map (context);
|
context.command_map = make_command_map (context);
|
||||||
|
|
||||||
if (opts->eval) { // evaluate command-line command & exit
|
if (opts->eval) { // evaluate command-line command & exit
|
||||||
auto call{Sexp::parse(opts->eval)};
|
auto call{Sexp::Node::make(opts->eval)};
|
||||||
invoke(context.command_map, call);
|
invoke(context.command_map, call);
|
||||||
return MU_OK;
|
return MU_OK;
|
||||||
}
|
}
|
||||||
@ -1306,10 +1352,11 @@ mu_cmd_server (MuConfig *opts, GError **err) try
|
|||||||
if (line.find_first_not_of(" \t") == std::string::npos)
|
if (line.find_first_not_of(" \t") == std::string::npos)
|
||||||
continue; // skip whitespace-only lines
|
continue; // skip whitespace-only lines
|
||||||
|
|
||||||
auto call{Sexp::parse(line)};
|
auto call{Sexp::Node::make(line)};
|
||||||
|
|
||||||
invoke(context.command_map, call);
|
invoke(context.command_map, call);
|
||||||
|
|
||||||
|
save_line(line);
|
||||||
|
|
||||||
} catch (const Error& er) {
|
} catch (const Error& er) {
|
||||||
std::cerr << ";; error: " << er.what() << "\n";
|
std::cerr << ";; error: " << er.what() << "\n";
|
||||||
print_error ((MuError)er.code(), "%s (line was:'%s')",
|
print_error ((MuError)er.code(), "%s (line was:'%s')",
|
||||||
|
|||||||
@ -671,13 +671,15 @@ This is used by the completion function in mu4e-compose."
|
|||||||
:size (length contacts))))
|
:size (length contacts))))
|
||||||
(dolist (contact contacts)
|
(dolist (contact contacts)
|
||||||
(cl-incf n)
|
(cl-incf n)
|
||||||
(let ((address
|
(let* ((address (plist-get contact :address))
|
||||||
|
(address
|
||||||
(if (functionp mu4e-contact-process-function)
|
(if (functionp mu4e-contact-process-function)
|
||||||
(funcall mu4e-contact-process-function (car contact))
|
(funcall mu4e-contact-process-function address)
|
||||||
(car contact))))
|
address)))
|
||||||
(when address ;; note the explicit deccode; the strings we get are utf-8,
|
(when address ;; note the explicit deccode; the strings we get are utf-8,
|
||||||
;; but emacs doesn't know yet.
|
;; but emacs doesn't know yet.
|
||||||
(puthash (decode-coding-string address 'utf-8) (cdr contact) mu4e~contacts))))
|
(puthash (decode-coding-string address 'utf-8)
|
||||||
|
(plist-get contact :rank) mu4e~contacts))))
|
||||||
|
|
||||||
(setq mu4e~contacts-tstamp (or tstamp "0"))
|
(setq mu4e~contacts-tstamp (or tstamp "0"))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user