[geary/mjog/mail-merge-plugin: 74/74] MailMerge: Implement initial email merging
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/mjog/mail-merge-plugin: 74/74] MailMerge: Implement initial email merging
- Date: Fri, 14 Aug 2020 02:09:05 +0000 (UTC)
commit 73be92cfb8496babd06fa0550d430038e83b094b
Author: Michael Gratton <mike vee net>
Date: Mon Aug 10 11:31:59 2020 +1000
MailMerge: Implement initial email merging
Make `Processor` an instantiable class to handle merging from a
template email. Use this in `Folder` to implement merging for each
data row.
.../plugin/mail-merge/mail-merge-folder.vala | 6 +-
.../plugin/mail-merge/mail-merge-processor.vala | 129 ++++++++++++++++++++-
.../mail-merge/mail-merge-test-processor.vala | 29 ++++-
3 files changed, 160 insertions(+), 4 deletions(-)
---
diff --git a/src/client/plugin/mail-merge/mail-merge-folder.vala
b/src/client/plugin/mail-merge/mail-merge-folder.vala
index 8b54b37af..bacc9216a 100644
--- a/src/client/plugin/mail-merge/mail-merge-folder.vala
+++ b/src/client/plugin/mail-merge/mail-merge-folder.vala
@@ -264,7 +264,11 @@ public class MailMerge.Folder : Geary.AbstractLocalFolder {
for (int i = 0; i < headers.length; i++) {
fields.set(headers[i], record[i]);
}
- var message = new Geary.RFC822.Message.from_buffer(raw_rfc822);
+ var processor = new Processor(this.template);
+ var composed = processor.merge(fields);
+ var message = yield new Geary.RFC822.Message.from_composed_email(
+ composed, null, cancellable
+ );
var id = new EmailIdentifier(next_id++);
var email = new Geary.Email.from_message(id, message);
diff --git a/src/client/plugin/mail-merge/mail-merge-processor.vala
b/src/client/plugin/mail-merge/mail-merge-processor.vala
index 90c4fe010..8e72d651c 100644
--- a/src/client/plugin/mail-merge/mail-merge-processor.vala
+++ b/src/client/plugin/mail-merge/mail-merge-processor.vala
@@ -11,8 +11,9 @@
public class MailMerge.Processor : GLib.Object {
- public const Geary.Email.Field REQUIRED_FIELDS =
- Geary.Email.REQUIRED_FOR_MESSAGE;
+ public const Geary.Email.Field REQUIRED_FIELDS = (
+ ENVELOPE | Geary.Email.REQUIRED_FOR_MESSAGE
+ );
private const string FIELD_START = "{{";
@@ -143,4 +144,128 @@ public class MailMerge.Processor : GLib.Object {
return found;
}
+
+ /** The email template being processed. */
+ public Geary.Email template {
+ get; private set;
+ }
+
+ /** The email constructed by the processor. */
+ public Geary.ComposedEmail? email {
+ get; private set; default = null;
+ }
+
+ /** A list of data fields missing when processing the template. */
+ public Gee.List<string> missing_fields {
+ get; private set; default = new Gee.LinkedList<string>();
+ }
+
+ /** Constructs a new merge processor with the given template. */
+ public Processor(Geary.Email template) {
+ this.template = template;
+ }
+
+ /**
+ * Merges the template with the given data to produce a complete message.
+ *
+ * Creates a new composed email based on the template and given
+ * data, set it as the {@email} property and returns it.
+ */
+ public Geary.ComposedEmail merge(Gee.Map<string,string> values)
+ throws GLib.Error {
+ var from = format_mailbox_addresses(this.template.from, values);
+ var email = this.email = new Geary.ComposedEmail(
+ new GLib.DateTime.now(), from
+ );
+ email.set_to(format_mailbox_addresses(this.template.to, values));
+ email.set_cc(format_mailbox_addresses(this.template.cc, values));
+ email.set_bcc(format_mailbox_addresses(this.template.bcc, values));
+ email.set_reply_to(format_mailbox_addresses(this.template.reply_to, values));
+ email.set_sender(format_mailbox_address(this.template.sender, values));
+ if (this.template.subject != null) {
+ email.set_subject(
+ format_string(this.template.subject.value, values)
+ );
+ }
+ email.set_in_reply_to(this.template.in_reply_to);
+ email.set_references(this.template.references);
+ // Don't set the Message-ID since it should be per-recipient
+
+ var message = this.template.get_message();
+ if (message.has_plain_body()) {
+ email.body_text = format_string(
+ message.get_plain_body(false, null), values
+ );
+ }
+ if (message.has_html_body()) {
+ email.body_html = format_string(
+ message.get_html_body(null), values
+ );
+ }
+
+ return email;
+ }
+
+ private inline Geary.RFC822.MailboxAddresses? format_mailbox_addresses(
+ Geary.RFC822.MailboxAddresses? addresses,
+ Gee.Map<string,string> values
+ ) {
+ Geary.RFC822.MailboxAddresses? formatted = null;
+ if (addresses != null && !addresses.is_empty) {
+ formatted = new Geary.RFC822.MailboxAddresses();
+ foreach (var addr in addresses) {
+ formatted = formatted.merge_mailbox(
+ format_mailbox_address(addr, values)
+ );
+ }
+ }
+ return formatted;
+ }
+
+ private inline Geary.RFC822.MailboxAddress? format_mailbox_address(
+ Geary.RFC822.MailboxAddress? address,
+ Gee.Map<string,string> values
+ ) {
+ Geary.RFC822.MailboxAddress? formatted = null;
+ if (address != null) {
+ formatted = new Geary.RFC822.MailboxAddress(
+ format_string(address.name, values),
+ format_string(address.address, values)
+ );
+ }
+ return formatted;
+ }
+
+ private inline string format_string(string? text,
+ Gee.Map<string,string> values) {
+ string? formatted = null;
+ if (text != null) {
+ var buf = new GLib.StringBuilder.sized(text.length);
+ var parser = Parser(text);
+
+ while (!parser.spent) {
+ string? value = null;
+ if (parser.at_field_start) {
+ var field = parser.read_field();
+ if (parser.at_field_end) {
+ // found end-of-field-delim, look it up
+ value = values.get(field);
+ if (value == null) {
+ this.missing_fields.add(field);
+ value = to_field(field);
+ }
+ } else {
+ // didn't find end-of-field-delim, treat as text
+ value = field;
+ }
+ } else {
+ value = parser.read_text();
+ }
+ buf.append(value);
+ }
+ formatted = buf.str;
+ }
+ return formatted;
+ }
+
}
diff --git a/src/client/plugin/mail-merge/mail-merge-test-processor.vala
b/src/client/plugin/mail-merge/mail-merge-test-processor.vala
index d1c34f93e..16d4a09b3 100644
--- a/src/client/plugin/mail-merge/mail-merge-test-processor.vala
+++ b/src/client/plugin/mail-merge/mail-merge-test-processor.vala
@@ -53,6 +53,8 @@ public class MailMerge.TestProcessor : ValaUnit.TestCase {
base("MailMerge.TestProcessor");
add_test("contains_field", contains_field);
add_test("is_mail_merge_template", is_mail_merge_template);
+ add_test("identity_merge", identity_merge);
+ add_test("subject_merge", subject_merge);
}
public void contains_field() throws GLib.Error {
@@ -85,6 +87,31 @@ public class MailMerge.TestProcessor : ValaUnit.TestCase {
);
}
+ public void identity_merge() throws GLib.Error {
+ var original = string_to_email(TEMPLATE_SUBJECT);
+ var processor = new Processor(original);
+ var merged = processor.merge(Gee.Map.empty<string,string>());
+
+ assert_equal(merged.from.to_rfc822_string(), original.from.to_rfc822_string());
+ assert_equal(merged.reply_to.to_rfc822_string(), original.reply_to.to_rfc822_string());
+ assert_equal(merged.sender.to_rfc822_string(), original.sender.to_rfc822_string());
+ assert_equal(merged.to.to_rfc822_string(), original.to.to_rfc822_string());
+ assert_equal(merged.cc.to_rfc822_string(), original.cc.to_rfc822_string());
+ assert_equal(merged.bcc.to_rfc822_string(), original.bcc.to_rfc822_string());
+ assert_equal(merged.subject.to_rfc822_string(), original.subject.to_rfc822_string());
+ assert_equal(merged.body_text, original.get_message().get_plain_body(false, null));
+ }
+
+ public void subject_merge() throws GLib.Error {
+ var original = string_to_email(TEMPLATE_SUBJECT);
+ var processor = new Processor(original);
+ var merged = processor.merge(
+ Geary.Collection.single_map("subject", "test")
+ );
+
+ assert_equal(merged.subject.value, "test");
+ }
+
private Geary.Email string_to_email(string message_text)
throws GLib.Error {
var message = new Geary.RFC822.Message.from_buffer(
@@ -120,7 +147,7 @@ To: Charlie <charlie example net>
CC: Dave <dave example net>
BCC: Eve <eve example net>
Reply-To: "Alice: Personal Account" <alice example org>
-Subject: {{hello}}
+Subject: {{subject}}
Date: Fri, 21 Nov 1997 10:01:10 -0600
Message-ID: <3456 example net>
In-Reply-To: <1234@local.machine.example>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]