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:
14
NEWS.org
14
NEWS.org
@ -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:
|
||||||
|
|||||||
@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
Reference in New Issue
Block a user