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,
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
database-corruption. In *mu4e* that means you need to _wait_ until indexing is
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
longer, the suffix has been removed.
- the cleanup phase after indexing is significantly faster now
*** mu4e
- message composition has been completely reworked to avoid a number of
@ -69,9 +73,9 @@
result you expected.
- When you ask for bookmarks or maildirs through ~mu4e-search-bookmark~ or
~mu4e-search-maildir~, unread counts are displayed in the (default) completions
UI next to the maildir or bookmark. If you don't want to see these counts,
set ~mu4e-hide-short-counts~ to non-~nil~.
~mu4e-search-maildir~, unread counts are displayed in the (default)
completions UI next to the maildir or bookmark. If you don't want to see
these counts, set ~mu4e-hide-short-counts~ to non-~nil~.
- A (experimental) "transient" menu has been added for mu4e. You can use it
e.g., with something like:

View File

@ -228,6 +228,13 @@ Sexp::to_json_string(Format fopts) const
{
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()) {
case Type::List: {
// property-lists become JSON objects
@ -237,16 +244,18 @@ Sexp::to_json_string(Format fopts) const
bool first{true};
while (it != list().end()) {
const auto key{it->symbol().name};
sstrm << (first ? "" : ",") << quote(key) << ":";
sstrm << (first ? "" : ",")
<< quote(sym_name(key)) << ":";
++it;
const auto emacs_tstamp{*it};
sstrm << emacs_tstamp.to_json_string();
sstrm << emacs_tstamp.to_json_string(fopts);
++it;
first = false;
// special-case: tstamp-fields also get a "unix" value,
// which are easier to work with than the "emacs" timestamps
if (key == ":date" || key == ":changed")
sstrm << "," << quote(key + "-unix") << ":"
sstrm << "," << quote(sym_name(key) + "-unix")
<< ":"
<< unix_tstamp(emacs_tstamp);
}
sstrm << "}";
@ -256,7 +265,7 @@ Sexp::to_json_string(Format fopts) const
sstrm << '[';
bool first{true};
for (auto&& child : list()) {
sstrm << (first ? "" : ", ") << child.to_json_string();
sstrm << (first ? "" : ", ") << child.to_json_string(fopts);
first = false;
}
sstrm << ']';
@ -287,7 +296,6 @@ Sexp::to_json_string(Format fopts) const
}
Sexp&
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
** under the terms of the GNU General Public License as published by the
@ -264,6 +264,8 @@ struct Sexp {
Default = 0, /**< Nothing in particular */
SplitList = 1 << 0, /**< Insert newline after list item */
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
*/
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);

View File

@ -34,8 +34,9 @@ would find all messages in 2009 with `snow' in the subject field, e.g:
#+end_example
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
s-expressions), see the discussion in the *OPTIONS*-section below about *--format*.
have to use *--format=plain*. For other types of output (such as symlinks, XML,
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
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.
** -n, --maxnum _number_
If _number_ > 0, display maximally that number of entries. If not specified, all
matching entries are displayed.
** --summary-len _number_
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.
@ -131,8 +134,13 @@ Output results in the specified format.
- *xml* formats the search results as XML.
- *sexp* formats the search results as an s-expression as used in Lisp programming
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
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
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*.
** --after _timestamp_
Only show messages whose message files were last modified (*mtime*) after
_timestamp_. _timestamp_ is a UNIX *time_t* value, the number of seconds since
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
default format), and UTF-8 for all other formats (=sexp=, =xml=).
* PERFORMANCE
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)
2. time (repeat 10 mu find "" -n 50000 --include-related --threads > /dev/null)
#+ATTR_MAN: :disable-caption t
| 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.11 (master) | 10.1s | 29.5s |
#+include: "exit-code.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
** 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)
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();
}
@ -443,6 +446,7 @@ get_output_func(const Options& opts)
case Format::Sexp:
return output_sexp;
case Format::Json:
case Format::Json2:
return output_json;
default:
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
** 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,
{"json", "JSON"}
},
{ Format::Json2,
{"json2", "more idiomatic JSON"}
},
}};
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
** under the terms of the GNU General Public License as published by the
@ -149,7 +149,7 @@ struct Options {
std::string bookmark; /**< use bookmark */
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 */
std::string exec; /**< cmd to execute on matches */
bool skip_dups; /**< show only first with msg id */