mu-find: implement format=json2

Implement a new experimental json2 format for mu-find, which gets rid of
the ":" prefixes for fields, i.e., "subject" instead of ":subject".
Document it as well.
This commit is contained in:
Dirk-Jan C. Binnema
2025-03-23 19:43:07 +02:00
parent d99785ed35
commit 44ba631a34
7 changed files with 52 additions and 28 deletions

View File

@ -35,6 +35,12 @@
is a bit easier to manipulate than the emacs-style timestamps (same value, is a bit easier to manipulate than the emacs-style timestamps (same value,
but expressed as a list). but expressed as a list).
- With 1.12.10, there is also an experimental ~--format=json2~, where key-names
are no longer prefixed with ~:~, so for example, "*subject"* instead of
"*:subject"*.
- With 1.12.9, the cleanup phase after indexing is significantly faster now
- with 1.12.7, ~mu~ indexing is single-threaded again, to avoid cases of - with 1.12.7, ~mu~ indexing is single-threaded again, to avoid cases of
database-corruption. In *mu4e* that means you need to _wait_ until indexing is database-corruption. In *mu4e* that means you need to _wait_ until indexing is
ready before you can continue (*mu4e* will warn you). ready before you can continue (*mu4e* will warn you).
@ -47,8 +53,6 @@
in the mu4e version in its main-page; however, since this is optional no in the mu4e version in its main-page; however, since this is optional no
longer, the suffix has been removed. longer, the suffix has been removed.
- the cleanup phase after indexing is significantly faster now
*** mu4e *** mu4e
- message composition has been completely reworked to avoid a number of - message composition has been completely reworked to avoid a number of
@ -69,9 +73,9 @@
result you expected. result you expected.
- When you ask for bookmarks or maildirs through ~mu4e-search-bookmark~ or - When you ask for bookmarks or maildirs through ~mu4e-search-bookmark~ or
~mu4e-search-maildir~, unread counts are displayed in the (default) completions ~mu4e-search-maildir~, unread counts are displayed in the (default)
UI next to the maildir or bookmark. If you don't want to see these counts, completions UI next to the maildir or bookmark. If you don't want to see
set ~mu4e-hide-short-counts~ to non-~nil~. these counts, set ~mu4e-hide-short-counts~ to non-~nil~.
- A (experimental) "transient" menu has been added for mu4e. You can use it - A (experimental) "transient" menu has been added for mu4e. You can use it
e.g., with something like: e.g., with something like:

View File

@ -228,6 +228,13 @@ Sexp::to_json_string(Format fopts) const
{ {
std::stringstream sstrm; std::stringstream sstrm;
const auto sym_name=[&](const std::string& sym) {
if (any_of(fopts & Format::NoColon) && sym[0] == ':')
return sym.substr(1); // remove colon
else
return sym;
};
switch (type()) { switch (type()) {
case Type::List: { case Type::List: {
// property-lists become JSON objects // property-lists become JSON objects
@ -237,16 +244,18 @@ Sexp::to_json_string(Format fopts) const
bool first{true}; bool first{true};
while (it != list().end()) { while (it != list().end()) {
const auto key{it->symbol().name}; const auto key{it->symbol().name};
sstrm << (first ? "" : ",") << quote(key) << ":"; sstrm << (first ? "" : ",")
<< quote(sym_name(key)) << ":";
++it; ++it;
const auto emacs_tstamp{*it}; const auto emacs_tstamp{*it};
sstrm << emacs_tstamp.to_json_string(); sstrm << emacs_tstamp.to_json_string(fopts);
++it; ++it;
first = false; first = false;
// special-case: tstamp-fields also get a "unix" value, // special-case: tstamp-fields also get a "unix" value,
// which are easier to work with than the "emacs" timestamps // which are easier to work with than the "emacs" timestamps
if (key == ":date" || key == ":changed") if (key == ":date" || key == ":changed")
sstrm << "," << quote(key + "-unix") << ":" sstrm << "," << quote(sym_name(key) + "-unix")
<< ":"
<< unix_tstamp(emacs_tstamp); << unix_tstamp(emacs_tstamp);
} }
sstrm << "}"; sstrm << "}";
@ -256,7 +265,7 @@ Sexp::to_json_string(Format fopts) const
sstrm << '['; sstrm << '[';
bool first{true}; bool first{true};
for (auto&& child : list()) { for (auto&& child : list()) {
sstrm << (first ? "" : ", ") << child.to_json_string(); sstrm << (first ? "" : ", ") << child.to_json_string(fopts);
first = false; first = false;
} }
sstrm << ']'; sstrm << ']';
@ -287,7 +296,6 @@ Sexp::to_json_string(Format fopts) const
} }
Sexp& Sexp&
Sexp::del_prop(const std::string& pname) Sexp::del_prop(const std::string& pname)
{ {

View File

@ -1,5 +1,5 @@
/* /*
** Copyright (C) 2020-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** Copyright (C) 2020-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** **
** This program is free software; you can redistribute it and/or modify it ** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the ** under the terms of the GNU General Public License as published by the
@ -264,6 +264,8 @@ struct Sexp {
Default = 0, /**< Nothing in particular */ Default = 0, /**< Nothing in particular */
SplitList = 1 << 0, /**< Insert newline after list item */ SplitList = 1 << 0, /**< Insert newline after list item */
TypeInfo = 1 << 1, /**< Show type-info */ TypeInfo = 1 << 1, /**< Show type-info */
NoColon = 1 << 2, /**< In the Json output, remove the colon
* prefixes for property keys */
}; };
/** /**
@ -272,7 +274,7 @@ struct Sexp {
* @return str * @return str
*/ */
std::string to_string(Format fopts=Format::Default) const; std::string to_string(Format fopts=Format::Default) const;
std::string to_json_string(Format fopts=Format::Default) const; std::string to_json_string(Format fopts) const;
Sexp& del_prop(const std::string& pname); Sexp& del_prop(const std::string& pname);
@ -280,7 +282,7 @@ struct Sexp {
* Some useful constants * Some useful constants
* *
*/ */
static inline const auto nil_sym = Sexp::Symbol{"nil"}; static inline const auto nil_sym = Sexp::Symbol{"nil"};
static inline const auto t_sym = Sexp::Symbol{"t"}; static inline const auto t_sym = Sexp::Symbol{"t"};
protected: protected:

View File

@ -34,8 +34,9 @@ would find all messages in 2009 with `snow' in the subject field, e.g:
#+end_example #+end_example
Note, this the default, plain-text output, which is the default, so you don't Note, this the default, plain-text output, which is the default, so you don't
have to use *--format=plain*. For other types of output (such as symlinks, XML or have to use *--format=plain*. For other types of output (such as symlinks, XML,
s-expressions), see the discussion in the *OPTIONS*-section below about *--format*. s-expressions or JSON), see the discussion in the *OPTIONS*-section below about
*--format*.
The search pattern is taken as a command-line parameter. If the search The search pattern is taken as a command-line parameter. If the search
parameter consists of multiple parts (as in the example) they are parameter consists of multiple parts (as in the example) they are
@ -114,13 +115,15 @@ Note, if you specify a sortfield, by default, messages are sorted in reverse
choice, but for dates it may be more useful to sort in the opposite direction. choice, but for dates it may be more useful to sort in the opposite direction.
** -n, --maxnum _number_ ** -n, --maxnum _number_
If _number_ > 0, display maximally that number of entries. If not specified, all If _number_ > 0, display maximally that number of entries. If not specified, all
matching entries are displayed. matching entries are displayed.
** --summary-len _number_ ** --summary-len _number_
If _number_ > 0, use that number of lines of the message to provide a summary. If _number_ > 0, use that number of lines of the message to provide a summary.
** --format plain|links|xml|sexp ** --format plain|links|xml|sexp|json|json2
Output results in the specified format. Output results in the specified format.
@ -131,8 +134,13 @@ Output results in the specified format.
- *xml* formats the search results as XML. - *xml* formats the search results as XML.
- *sexp* formats the search results as an s-expression as used in Lisp programming - *sexp* formats the search results as an s-expression as used in Lisp programming
environments. environments.
- *json* formats the output as JSON; it is a direct translation of the *sexp* format
- *json2* is a slightly more idiomatic JSON, for now the only difference with *json*
is that the latter avoids the ':' prefix in key-names (e.g., "subject" instead
of ":subject"). *json2* is still experimental.
** --linksdir _dir_ and -c, --clearlinks ** --linksdir _dir_ and -c, --clearlinks
When using *--format=links*, output the results as a maildir with symbolic links When using *--format=links*, output the results as a maildir with symbolic links
to the found messages. This enables easy integration with mail-clients (see to the found messages. This enables easy integration with mail-clients (see
below for more information). *mu* will create the maildir if it does not exist below for more information). *mu* will create the maildir if it does not exist
@ -152,6 +160,7 @@ it automatically inserts a _.noindex_ file, to exclude the directory from *mu
index*. index*.
** --after _timestamp_ ** --after _timestamp_
Only show messages whose message files were last modified (*mtime*) after Only show messages whose message files were last modified (*mtime*) after
_timestamp_. _timestamp_ is a UNIX *time_t* value, the number of seconds since _timestamp_. _timestamp_ is a UNIX *time_t* value, the number of seconds since
1970-01-01 (in UTC). 1970-01-01 (in UTC).
@ -279,8 +288,6 @@ After restarting Wanderlust, the virtual folders should appear.
*mu find* output is encoded according to the locale for *--format=plain* (the *mu find* output is encoded according to the locale for *--format=plain* (the
default format), and UTF-8 for all other formats (=sexp=, =xml=). default format), and UTF-8 for all other formats (=sexp=, =xml=).
* PERFORMANCE * PERFORMANCE
Some notes on performance, comparing the timings between some recent releases; Some notes on performance, comparing the timings between some recent releases;
@ -289,7 +296,6 @@ taking the total number for 10 test runs.
1. time (repeat 10 mu find "" -n 50000 > /dev/null) 1. time (repeat 10 mu find "" -n 50000 > /dev/null)
2. time (repeat 10 mu find "" -n 50000 --include-related --threads > /dev/null) 2. time (repeat 10 mu find "" -n 50000 --include-related --threads > /dev/null)
#+ATTR_MAN: :disable-caption t #+ATTR_MAN: :disable-caption t
| release | time 1 (sec) | time 2 (sec) | | release | time 1 (sec) | time 2 (sec) |
|---------------+--------------+--------------| |---------------+--------------+--------------|
@ -299,9 +305,6 @@ taking the total number for 10 test runs.
| 1.10 | 9.8s | 30.6s | | 1.10 | 9.8s | 30.6s |
| 1.11 (master) | 10.1s | 29.5s | | 1.11 (master) | 10.1s | 29.5s |
#+include: "exit-code.inc" :minlevel 1 #+include: "exit-code.inc" :minlevel 1
#+include: "bugs.inc" :minlevel 1 #+include: "bugs.inc" :minlevel 1

View File

@ -1,5 +1,5 @@
/* /*
** Copyright (C) 2008-2024 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** Copyright (C) 2008-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** **
** This program is free software; you can redistribute it and/or modify it ** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the ** under the terms of the GNU General Public License as published by the
@ -383,7 +383,10 @@ output_json(const Option<Message>& msg, const OutputInfo& info, const Options& o
if (!msg) if (!msg)
return Ok(); return Ok();
mu_println("{}{}", msg->sexp().to_json_string(), info.last ? "" : ","); const Sexp::Format frm{opts.find.format == Format::Json2 ? Sexp::Format::NoColon :
Sexp::Format::Default};
mu_println("{}{}", msg->sexp().to_json_string(frm), info.last ? "" : ",");
return Ok(); return Ok();
} }
@ -443,6 +446,7 @@ get_output_func(const Options& opts)
case Format::Sexp: case Format::Sexp:
return output_sexp; return output_sexp;
case Format::Json: case Format::Json:
case Format::Json2:
return output_json; return output_json;
default: default:
throw Error(Error::Code::Internal, throw Error(Error::Code::Internal,

View File

@ -1,5 +1,5 @@
/* /*
** Copyright (C) 2022-2024 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** Copyright (C) 2022-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** **
** This program is free software; you can redistribute it and/or modify it ** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the ** under the terms of the GNU General Public License as published by the
@ -339,6 +339,9 @@ sub_find(CLI::App& sub, Options& opts)
{ Format::Json, { Format::Json,
{"json", "JSON"} {"json", "JSON"}
}, },
{ Format::Json2,
{"json2", "more idiomatic JSON"}
},
}}; }};
sub.add_flag("--threads,-t", opts.find.threads, sub.add_flag("--threads,-t", opts.find.threads,

View File

@ -1,5 +1,5 @@
/* /*
** Copyright (C) 2022-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** Copyright (C) 2022-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** **
** This program is free software; you can redistribute it and/or modify it ** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the ** under the terms of the GNU General Public License as published by the
@ -149,7 +149,7 @@ struct Options {
std::string bookmark; /**< use bookmark */ std::string bookmark; /**< use bookmark */
bool analyze; /**< analyze query */ bool analyze; /**< analyze query */
enum struct Format { Plain, Links, Xml, Json, Sexp, Exec }; enum struct Format { Plain, Links, Xml, Json, Json2, Sexp, Exec };
Format format; /**< Output format */ Format format; /**< Output format */
std::string exec; /**< cmd to execute on matches */ std::string exec; /**< cmd to execute on matches */
bool skip_dups; /**< show only first with msg id */ bool skip_dups; /**< show only first with msg id */
@ -256,7 +256,7 @@ struct Options {
bool terminate; /**< add \f between msgs in view */ bool terminate; /**< add \f between msgs in view */
OptSize summary_len; /**< max # of lines for summary */ OptSize summary_len; /**< max # of lines for summary */
enum struct Format { Plain, Sexp, Html }; enum struct Format { Plain, Sexp, Html };
Format format; /**< output format*/ Format format; /**< output format*/
StringVec files; /**< Message file(s) */ StringVec files; /**< Message file(s) */