diff --git a/CMakeLists.txt b/CMakeLists.txt index c77402d92..29b7232aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ set (CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") if (CMAKE_BUILD_TYPE STREQUAL "Debug") # debug build - retrieve current project version from git execute_process ( - COMMAND git describe --abbrev=8 --tags --always + COMMAND git -C "${CMAKE_SOURCE_DIR}" describe --abbrev=8 --tags --always OUTPUT_VARIABLE PROJECT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) diff --git a/Vagrantfile b/Vagrantfile index 29fbafb8c..e6a105938 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -43,20 +43,20 @@ Vagrant.configure("2") do |config| # the path on the host to the actual folder. The second argument is # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. - # config.vm.synced_folder "../data", "/vagrant_data" + # config.vm.synced_folder "~/.mail", "/vagrant/mail" # Provider-specific configuration so you can fine-tune various # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # - config.vm.provider "virtualbox" do |vb| - # Display the VirtualBox GUI when booting the machine - # vb.gui = true - - # Customize the amount of memory on the VM: - vb.memory = (3 * 1024).to_s - vb.cpus = 4 - end + config.vm.provider "virtualbox" do |vb| + # Display the VirtualBox GUI when booting the machine + # vb.gui = true + + # Customize the amount of memory on the VM: + vb.memory = (3 * 1024).to_s + vb.cpus = 4 + end # # View the documentation for the provider you are using for more # information on available options. @@ -69,7 +69,7 @@ Vagrant.configure("2") do |config| config.vm.provision "shell", inline: <<-SHELL pacman -Syu --noconfirm - pacman -S --noconfirm --needed cmake ninja git gcc notmuch-runtime glibmm gtkmm3 vte3 boost libsass libpeas ruby-ronn pkgconf webkit2gtk protobuf gobject-introspection xorg-xauth xorg-xclock cmark python-gobject ipython + pacman -S --noconfirm --needed cmake ninja git gcc notmuch-runtime glibmm gtkmm3 vte3 boost libsass libpeas ruby-ronn pkgconf webkit2gtk protobuf gobject-introspection xorg-xauth xorg-xclock cmark python-gobject ipython gvim cat > /etc/profile.d/astroid.sh <close (); delete actions; } + + logging::core::get()->remove_all_sinks (); } int Astroid::on_command_line (const refptr & cmd) { diff --git a/src/compose_message.cc b/src/compose_message.cc index 96330080b..a139e9223 100644 --- a/src/compose_message.cc +++ b/src/compose_message.cc @@ -157,7 +157,7 @@ namespace Astroid { sf << s.rdbuf (); s.close (); if (account->signature_separate) { - md_body_content += "-- \n"; + md_body_content += "-- \n"; } md_body_content += sf.str (); } @@ -228,6 +228,7 @@ namespace Astroid { contentStream = g_mime_stream_mem_new_with_buffer(_html.c_str(), _html.size()); } + g_spawn_close_pid (pid); } catch (Glib::SpawnError &ex) { LOG (error) << "cm: md: failed to spawn markdown processor: " << ex.what (); diff --git a/src/config.cc b/src/config.cc index 3830aceb4..0b7144073 100644 --- a/src/config.cc +++ b/src/config.cc @@ -235,6 +235,8 @@ namespace Astroid { default_config.put ("editor.markdown_processor", "cmark"); default_config.put ("editor.markdown_on", false); // default + default_config.put ("mail.reply.quote_processor", "w3m -dump -T text/html"); // e.g. lynx -dump + /* mail composition */ default_config.put ("mail.reply.quote_line", "Excerpts from %1's message of %2:"); // %1 = author, %2 = pretty_verbose_date default_config.put ("mail.reply.mailinglist_reply_to_sender", true); diff --git a/src/message_thread.cc b/src/message_thread.cc index a9c7712ec..fee529501 100644 --- a/src/message_thread.cc +++ b/src/message_thread.cc @@ -11,6 +11,7 @@ # include "message_thread.hh" # include "chunk.hh" # include "utils/utils.hh" +# include "utils/cmd.hh" # include "utils/date_utils.hh" # include "utils/address.hh" # include "utils/ustring_utils.hh" @@ -367,6 +368,67 @@ namespace Astroid { return body; } + ustring Message::quote () { + if (missing_content) { + LOG (warn) << "message: missing content, no text."; + return ""; + } + + ustring body; + + function< void (refptr) > app_body = + [&] (refptr c) + { + /* check if we're the preferred sibling */ + bool use = false; + + if (c->siblings.size() >= 1) { + if (c->is_content_type ("text", "plain") || c->is_content_type ("text", "html")) { + use = true; + } else { + /* check if there are any other preferred */ + if (all_of (c->siblings.begin (), + c->siblings.end (), + [](refptr c) { return !(c->is_content_type ("text", "plain") || c->is_content_type("text", "html")); })) { + use = true; // no + } else { + use = false; + } + } + } else { + use = true; + } + + if (use) { + if (c->viewable && (c->is_content_type ("text", "plain") || c->is_content_type ("text", "html"))) { + /* will output html if HTML part */ + if (c->is_content_type ("text", "html")) { + ustring quote_cmd = astroid->config ().get("mail.reply.quote_processor"); + + if (!quote_cmd.empty()) { + ustring h = c->viewable_text (false); + ustring _stdout, _stderr; + Cmd::pipe (quote_cmd, h, _stdout, _stderr); + + body += _stdout; + } + + } else { + body += c->viewable_text (false); + } + } + + for_each (c->kids.begin(), + c->kids.end (), + app_body); + } + }; + + app_body (root); + + return body; + } + vector> Message::attachments () { /* return a flat vector of attachments */ diff --git a/src/message_thread.hh b/src/message_thread.hh index f3b6ee32a..4bee766b9 100644 --- a/src/message_thread.hh +++ b/src/message_thread.hh @@ -68,6 +68,7 @@ namespace Astroid { std::vector tags; ustring plain_text (bool fallback_html = false); + ustring quote (); std::vector> attachments (); refptr get_chunk_by_id (int id); diff --git a/src/modes/edit_message.cc b/src/modes/edit_message.cc index f5a4d50f4..e0fa3bcc0 100644 --- a/src/modes/edit_message.cc +++ b/src/modes/edit_message.cc @@ -1258,7 +1258,7 @@ namespace Astroid { if (tmpfile.fail()) { LOG (error) << "em: error: could not create tmpfile!"; - throw runtime_error ("em: coult not create tmpfile!"); + throw runtime_error ("em: could not create tmpfile!"); } tmpfile.close (); diff --git a/src/modes/forward_message.cc b/src/modes/forward_message.cc index 8343b4268..e1b55c2da 100644 --- a/src/modes/forward_message.cc +++ b/src/modes/forward_message.cc @@ -67,7 +67,7 @@ namespace Astroid { quoted << "Cc: " << AddressList(msg->cc()).str () << endl; quoted << endl; - string vt = msg->plain_text (false); + string vt = msg->quote (); quoted << vt; body = ustring(quoted.str()); diff --git a/src/modes/reply_message.cc b/src/modes/reply_message.cc index 915c18e3d..6fae74535 100644 --- a/src/modes/reply_message.cc +++ b/src/modes/reply_message.cc @@ -56,7 +56,7 @@ namespace Astroid { quoted << quoting_a.raw () << endl; - string vt = msg->plain_text (false); + string vt = msg->quote (); stringstream sstr (vt); while (sstr.good()) { string line; diff --git a/src/utils/cmd.cc b/src/utils/cmd.cc index 49c298355..a9dba49bb 100644 --- a/src/utils/cmd.cc +++ b/src/utils/cmd.cc @@ -8,6 +8,7 @@ using std::endl; using std::string; + namespace bfs = boost::filesystem; namespace Astroid { @@ -44,9 +45,8 @@ namespace Astroid { ustring c = (!undo ? cmd : undo_cmd); LOG (info) << "cmd: running: " << c; - string _stdout; - string _stderr; int exit; + string _stdout, _stderr; string _cmd = c; try { @@ -71,6 +71,7 @@ namespace Astroid { return (exit == 0); } + ustring Cmd::substitute (const ustring _cmd) { ustring ncmd = _cmd; @@ -82,6 +83,58 @@ namespace Astroid { return ncmd; } + + bool Cmd::pipe (ustring cmd, const ustring& _stdin, ustring& _stdout, ustring &_stderr) { + LOG (info) << "cmd: running: " << cmd; + + try { + int pid; + int stdin; + int stdout; + int stderr; + std::vector args = Glib::shell_parse_argv (cmd); + + Glib::spawn_async_with_pipes ("", + args, + Glib::SPAWN_DO_NOT_REAP_CHILD | + Glib::SPAWN_SEARCH_PATH, + sigc::slot (), + &pid, + &stdin, + &stdout, + &stderr + ); + + refptr ch_stdin; + refptr ch_stdout; + refptr ch_stderr; + ch_stdin = Glib::IOChannel::create_from_fd (stdin); + ch_stdout = Glib::IOChannel::create_from_fd (stdout); + ch_stderr = Glib::IOChannel::create_from_fd (stderr); + + ch_stdin->write (_stdin); + ch_stdin->close (); + + ch_stderr->read_to_end (_stderr); + ch_stderr->close (); + + if (!_stderr.empty ()) { + LOG (error) << "cmd: " << _stderr; + } + + ch_stdout->read_to_end (_stdout); + ch_stdout->close (); + + g_spawn_close_pid (pid); + + } catch (Glib::SpawnError &ex) { + LOG (error) << "cmd: failed to execute: '" << cmd << "': " << ex.what (); + return false; + } + + + return true; + } } diff --git a/src/utils/cmd.hh b/src/utils/cmd.hh index cc28c0235..c65e9cfa9 100644 --- a/src/utils/cmd.hh +++ b/src/utils/cmd.hh @@ -1,11 +1,7 @@ # pragma once # include "astroid.hh" - -# include -# include -# include -# include +# include namespace Astroid { class Cmd { @@ -25,8 +21,12 @@ namespace Astroid { ustring undo_cmd; int execute (bool undo); /* currently only in sync */ + int execute (bool undo, std::string& _stdout, std::string& _stderr); /* currently only in sync */ ustring substitute (ustring); + + public: + static bool pipe (ustring cmd, const ustring& _stdin, ustring& _stdout, ustring& _stderr); }; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 59b7d99bf..106025c23 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -52,3 +52,5 @@ add_astroid_test (address test_address test_address.cc add_astroid_test (dates test_dates test_dates.cc ) add_astroid_test (crypto test_crypto test_crypto.cc ) add_astroid_test (gmime_version test_gmime_version test_gmime_version.cc ) +add_astroid_test (quote_html test_quote_html test_quote_html.cc ) + diff --git a/tests/test_quote_html.cc b/tests/test_quote_html.cc new file mode 100644 index 000000000..9ce12c032 --- /dev/null +++ b/tests/test_quote_html.cc @@ -0,0 +1,63 @@ +# define BOOST_TEST_DYN_LINK +# define BOOST_TEST_MODULE TestCompose +# include + +# include "test_common.hh" +# include "message_thread.hh" +# include "utils/ustring_utils.hh" +# include "config.hh" + +BOOST_AUTO_TEST_SUITE(QuoteHtml) + + BOOST_AUTO_TEST_CASE(quote_html) + { + using Astroid::Message; + setup (); + + ustring fname = "tests/mail/test_mail/only-html.eml"; + + Message m (fname); + ustring quoted = m.quote (); + + LOG (trace) << "quoted plain text: " << quoted; + + Astroid::UstringUtils::trim (quoted); + + ustring target = R"(1. save an email as file.eml + 2. write a new email + 3. attach file.eml + 4. save as draft + 5. quit astroid + 6. open draft again + 7. edit the draft + 8. here it is. + +— +You are receiving this because you commented. +Reply to this email directly, view it on GitHub, or mute the thread.*)"; + + BOOST_CHECK (quoted == target); + + teardown (); + } + + BOOST_AUTO_TEST_CASE(quote_html_convert_error) + { + using Astroid::Message; + setup (); + + // TODO: Does not work with lynx + /* const_cast(astroid->config()).put ("mail.reply.quote_processor", "lynx -dump -stdin"); */ + + ustring fname = "tests/mail/test_mail/isspace-fail-utf-8.eml"; + + Message m (fname); + ustring quoted = m.quote (); + + LOG (trace) << "quoted plain text: " << quoted; + + teardown (); + } + +BOOST_AUTO_TEST_SUITE_END() +