[geary/mjog/mail-merge-plugin: 2/10] MailMerge: Implement initial email merging




commit cec948413b5e0a8e7401595a59ccca22946bcb56
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]