From 44ba631a349fc7ab08bb066e1629c36b696ace59 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 23 Mar 2025 19:43:07 +0200 Subject: [PATCH] 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. --- NEWS.org | 14 +++++++++----- lib/utils/mu-sexp.cc | 18 +++++++++++++----- lib/utils/mu-sexp.hh | 8 +++++--- man/mu-find.1.org | 21 ++++++++++++--------- mu/mu-cmd-find.cc | 8 ++++++-- mu/mu-options.cc | 5 ++++- mu/mu-options.hh | 6 +++--- 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/NEWS.org b/NEWS.org index 41ca105e..aa24075e 100644 --- a/NEWS.org +++ b/NEWS.org @@ -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: diff --git a/lib/utils/mu-sexp.cc b/lib/utils/mu-sexp.cc index 6a173b7c..648e8ae2 100644 --- a/lib/utils/mu-sexp.cc +++ b/lib/utils/mu-sexp.cc @@ -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) { diff --git a/lib/utils/mu-sexp.hh b/lib/utils/mu-sexp.hh index 4ddd64f0..8ffa39bc 100644 --- a/lib/utils/mu-sexp.hh +++ b/lib/utils/mu-sexp.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2020-2023 Dirk-Jan C. Binnema +** Copyright (C) 2020-2025 Dirk-Jan C. Binnema ** ** 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); @@ -280,7 +282,7 @@ struct Sexp { * 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"}; protected: diff --git a/man/mu-find.1.org b/man/mu-find.1.org index da3e4d71..dfb65135 100644 --- a/man/mu-find.1.org +++ b/man/mu-find.1.org @@ -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 diff --git a/mu/mu-cmd-find.cc b/mu/mu-cmd-find.cc index ba7f779c..3556b454 100644 --- a/mu/mu-cmd-find.cc +++ b/mu/mu-cmd-find.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2008-2024 Dirk-Jan C. Binnema +** Copyright (C) 2008-2025 Dirk-Jan C. Binnema ** ** 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& 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, diff --git a/mu/mu-options.cc b/mu/mu-options.cc index 40a8c12a..1b58a28b 100644 --- a/mu/mu-options.cc +++ b/mu/mu-options.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2022-2024 Dirk-Jan C. Binnema +** Copyright (C) 2022-2025 Dirk-Jan C. Binnema ** ** 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, diff --git a/mu/mu-options.hh b/mu/mu-options.hh index fa440bf4..64398126 100644 --- a/mu/mu-options.hh +++ b/mu/mu-options.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2022-2023 Dirk-Jan C. Binnema +** Copyright (C) 2022-2025 Dirk-Jan C. Binnema ** ** 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 */ @@ -256,7 +256,7 @@ struct Options { bool terminate; /**< add \f between msgs in view */ OptSize summary_len; /**< max # of lines for summary */ - enum struct Format { Plain, Sexp, Html }; + enum struct Format { Plain, Sexp, Html }; Format format; /**< output format*/ StringVec files; /**< Message file(s) */