Author: gstein
Date: Mon Dec 25 02:04:08 2023
New Revision: 1914915

URL: http://svn.apache.org/viewvc?rev=1914915&view=rev
Log:
Adjust dataflow so that all output methods buffer the message content.
If this becomes too large (not yet implemented), then the buffer will
be truncated/reset and a short form of the contents will be created.

One goal is to minimize textual changes, so the start() and finish()
methods stick around. Now, rather than start() returning a Writer
instance, it will return a "prefix" for the message contents. finish()
now takes the entire message content and delivers it via the output
mechanism.

The buffer is held within the Writer instance, which is internally
constructed by the send() method. This can use a custom character
encoding (which only StandardOutput provides) via the new
.get_encoding() method on OutputBase.

* tools/hook-scripts/mailer/mailer.py:
  (Writer.__init__): no longer need WRITE_FUNC parameter, as all
    writing will be to the (new) internal BytesIO buffer. The ENCODING
    param no longer has a default -- it must be provided. The write
    functions are now derived from the BytesIO instance.
  (OutputBase.send): create our own Writer instance (a buffer). Reset
    that buffer if we switch to the short-format message. Then jigger
    up a use of .start() and .finish() to deliver the message.
  (OutputBase.get_encoding): new method to provide a custom encoding
    for a particular output channel.
  (OutputBase.finish): now takes a CONTENTS parameter for the complete
    contents of the message to deliver.
  (MailedOutput.start): return the .mail_headers() result as the
    prefix to use for emailed messages.
  (MailedOutput.mail_headers): return bytes(), not str()
  (SMTPOutput.start): removed. We can use the MailedOutput super.
  (SMTPOutput.finish): take CONTENTS and use instead of self.buffer
  (StandardOutput.get_encoding): override super to carry the encoding
    from STDOUT.
  (StandardOutput.start): return the prefix as bytes() rather than
    pushing them into a Writer instance.
  (StandardOutput.finish): push CONTENTS into STDOUT.
  (PipeOutput.start): removed. We can use the MailedOutput super.
  (PipeOutput.finish): push all contents into the pipe. This takes the
    bulk of .start() code to construct the pipe.

Modified:
    subversion/trunk/tools/hook-scripts/mailer/mailer.py

Modified: subversion/trunk/tools/hook-scripts/mailer/mailer.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/tools/hook-scripts/mailer/mailer.py?rev=1914915&r1=1914914&r2=1914915&view=diff
==============================================================================
--- subversion/trunk/tools/hook-scripts/mailer/mailer.py (original)
+++ subversion/trunk/tools/hook-scripts/mailer/mailer.py Mon Dec 25 02:04:08 
2023
@@ -168,17 +168,20 @@ def remove_leading_slashes(path):
 class Writer:
   "Simple class for writing strings/binary, with optional encoding."
 
-  def __init__(self, write_func, encoding='utf-8'):
-    self.write_binary = write_func
+  def __init__(self, encoding):
+    self.buffer = BytesIO()
+
+    # Attach a couple functions to SELF, rather than methods.
+    self.write_binary = self.buffer.write
 
     if codecs.lookup(encoding) != codecs.lookup('utf-8'):
       def _write(s):
         "Write text string S using the given encoding."
-        return write_func(s.encode(encoding, 'backslashreplace'))
+        return self.buffer.write(s.encode(encoding, 'backslashreplace'))
     else:
       def _write(s):
         "Write text string S using the *default* encoding (utf-8)."
-        return write_func(to_bytes(s))
+        return self.buffer.write(to_bytes(s))
     self.write = _write
 
 
@@ -191,20 +194,31 @@ class OutputBase:
     self._CHUNKSIZE = 128 * 1024
 
   def send(self, basic_subject, group, params, long_func, short_func):
-      writer = self.start(basic_subject, group, params)
+      writer = Writer(self.get_encoding())
 
       try:
           try:
               long_func(writer)
           except MessageTooLarge:
+              writer.buffer.truncate(0)
               short_func(writer)
-
-          self.finish()
       except MessageSendFailure:
         return True  # failed
 
+      ### use modified start/finish mechanism for minimal textual change.
+      prefix = self.start(basic_subject, group, params)
+      self.finish(prefix + writer.buffer.getvalue())
+
       return False  # succeeded
 
+  def get_encoding(self):
+    """Get the encoding for text-to-bytes in the output.
+
+    This will default to UTF-8. If the output mechanism needs a different
+    encoding, then override this method to provide the custom encoding.
+    """
+    return 'utf-8'
+
   def start(self, basic_subject, group, params):
     """Override this method.
 
@@ -216,11 +230,11 @@ class OutputBase:
     in the configuration file, plus the key 'author' contains the author
     of the action being reported.
 
-    Return a Writer instance.
+    Return bytes() for the prefix of the content to deliver.
     """
     raise NotImplementedError
 
-  def finish(self):
+  def finish(self, contents):
     """Override this method.
     Flush any cached information and finish writing the output
     representation."""
@@ -256,8 +270,8 @@ class MailedOutput(OutputBase):
                                and self.reply_to[2] == ']':
       self.reply_to = self.reply_to[3:]
 
-    ### NOTE: no Writer to return :(
-    return None
+    # Return the prefix for the mail message.
+    return self.mail_headers(subject_line, group)
 
   def _rfc2047_encode(self, hdr):
     # Return the result of splitting HDR into tokens (on space
@@ -299,23 +313,13 @@ class MailedOutput(OutputBase):
               os.path.basename(self.repos.repos_dir))
     if self.reply_to:
       hdrs = '%sReply-To: %s\n' % (hdrs, self.reply_to)
-    return hdrs + '\n'
+    return (hdrs + '\n').encode()
 
 
 class SMTPOutput(MailedOutput):
   "Deliver a mail message to an MTA using SMTP."
 
-  def start(self, subject_line, group, params):
-    MailedOutput.start(self, subject_line, group, params)
-
-    self.buffer = BytesIO()
-    writer = Writer(self.buffer.write)
-
-    writer.write(self.mail_headers(subject_line, group))
-
-    return writer
-
-  def finish(self):
+  def finish(self, contents):
     """
     Send email via SMTP or SMTP_SSL, logging in if username is
     specified.
@@ -355,7 +359,7 @@ class SMTPOutput(MailedOutput):
           # Any error at login is fatal
           raise
 
-      server.sendmail(self.from_addr, self.to_addrs, self.buffer.getvalue())
+      server.sendmail(self.from_addr, self.to_addrs, contents)
 
     ### TODO: 'raise .. from' is Python 3+. When we convert this
     ###       script to Python 3, uncomment 'from detail' below
@@ -388,17 +392,18 @@ class SMTPOutput(MailedOutput):
 class StandardOutput(OutputBase):
   "Print the commit message to stdout."
 
-  def start(self, subject_line, group, params):
-    encoding = sys.stdout.encoding if PY3 else 'utf-8'
-    writer = Writer(_stdout.write, encoding)
+  def get_encoding(self):
+    return sys.stdout.encoding if PY3 else 'utf-8'
 
-    writer.write("Group: " + (group or "defaults") + "\n")
-    writer.write("Subject: %s\n\n" % (subject_line,))
+  def start(self, subject_line, group, params):
+    return (
+        ("Group: " + (group or "defaults") + "\n")
+      + ("Subject: %s\n\n" % (subject_line,))
+      ).encode()
 
-    return writer
+  def finish(self, contents):
+    _stdout.write(contents)
 
-  def finish(self):
-    pass
 
 
 class PipeOutput(MailedOutput):
@@ -410,9 +415,7 @@ class PipeOutput(MailedOutput):
     # figure out the command for delivery
     self.cmd = cfg.general.mail_command.split()
 
-  def start(self, subject_line, group, params):
-    MailedOutput.start(self, subject_line, group, params)
-
+  def finish(self, contents):
     ### gotta fix this. this is pretty specific to sendmail and qmail's
     ### mailwrapper program. should be able to use option param substitution
     cmd = self.cmd + [ '-f', self.from_addr ] + self.to_addrs
@@ -420,14 +423,8 @@ class PipeOutput(MailedOutput):
     # construct the pipe for talking to the mailer
     self.pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                                  close_fds=sys.platform != "win32")
-    writer = Writer(self.pipe.stdin.write)
-
-    # start writing out the mail message
-    writer.write(self.mail_headers(subject_line, group))
-
-    return writer
+    self.pipe.write(contents)
 
-  def finish(self):
     # signal that we're done sending content
     self.pipe.stdin.close()
 


Reply via email to