Author: gstein
Date: Sun Oct 15 07:19:29 2023
New Revision: 1912978
URL: http://svn.apache.org/viewvc?rev=1912978&view=rev
Log:
Reorganize mechanisms for writing to the output, using new Writer class.
The goal is to encapsulate the output stream's ENCODING into a writer
class. In the future, this class will also track total bytes written
in order to limit (email) message size.
* class Writer: new. Expose .write_binary() and .write(). For the
latter, use the PY2/3 algorithm that was in StandardOutput to
handle the encoding of text into a binary output.
* OutputBase.start(): return a Writer instance for the caller to write
to the newly-started output.
* OutputBase.write() and .write_binary(): removed. These are moved to
the Writer class, which is now a distinct object in the dataflow.
* MailedOutput.__init__(): removed as it adds no value.
* MailedOutput.start(): note it does not return a Writer as the docco
says it should. This class shouldn't be in this hierarchy. TBD
* SMTPOutput.start(): return a Writer instance
* StandardOutput.__init__(): removed as it adds no value, after
removing the .write_binary attribute initialization in favor of a
Writer returned from .start()
* StandardOutput.start(): return a Writer instance
* StandardOutput.write(): remove the logic that conditionally created
a .write() method. This is part of the Writer class now.
* PipeOutput.start(): return a Writer instance.
* Commit.generate(), PropChange.generate(), Lock.generate(): use the
Writer returned from OUTPUT.start()
* generate_content(): accept a Write instance rather than an OUTPUT,
and update its single caller.
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=1912978&r1=1912977&r2=1912978&view=diff
==============================================================================
--- subversion/trunk/tools/hook-scripts/mailer/mailer.py (original)
+++ subversion/trunk/tools/hook-scripts/mailer/mailer.py Sun Oct 15 07:19:29
2023
@@ -153,6 +153,23 @@ def remove_leading_slashes(path):
return 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
+
+ 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'))
+ else:
+ def _write(s):
+ "Write text string S using the *default* encoding (utf-8)."
+ return write_func(to_bytes(s))
+ self.write = _write
+
+
class OutputBase:
"Abstract base class to formalize the interface of output methods"
@@ -197,7 +214,9 @@ class OutputBase:
configuration file group which is causing this output to be produced.
PARAMS is a dictionary of any named subexpressions of regular expressions
defined in the configuration file, plus the key 'author' contains the
- author of the action being reported."""
+ author of the action being reported.
+
+ Return a Writer instance."""
raise NotImplementedError
def finish(self):
@@ -206,15 +225,6 @@ class OutputBase:
representation."""
raise NotImplementedError
- def write_binary(self, output):
- """Override this method.
- Append the binary data OUTPUT to the output representation."""
- raise NotImplementedError
-
- def write(self, output):
- """Append the literal text string OUTPUT to the output representation."""
- return self.write_binary(to_bytes(output))
-
def run(self, cmd):
"""Override this method, if the default implementation is not sufficient.
Execute CMD, writing the stdout produced to the output representation."""
@@ -233,8 +243,6 @@ class OutputBase:
class MailedOutput(OutputBase):
- def __init__(self, cfg, repos, prefix_param):
- OutputBase.__init__(self, cfg, repos, prefix_param)
def start(self, group, params):
# whitespace (or another character) separated list of addresses
@@ -263,6 +271,9 @@ class MailedOutput(OutputBase):
and self.reply_to[2] == ']':
self.reply_to = self.reply_to[3:]
+ ### NOTE: no Writer to return :(
+ return None
+
def _rfc2047_encode(self, hdr):
# Return the result of splitting HDR into tokens (on space
# characters), encoding (per RFC2047) each token as necessary, and
@@ -313,9 +324,11 @@ class SMTPOutput(MailedOutput):
MailedOutput.start(self, group, params)
self.buffer = BytesIO()
- self.write_binary = self.buffer.write
+ writer = Writer(self.buffer.write)
+
+ writer.write(self.mail_headers(group, params))
- self.write(self.mail_headers(group, params))
+ return writer
def finish(self):
"""
@@ -390,23 +403,18 @@ class SMTPOutput(MailedOutput):
class StandardOutput(OutputBase):
"Print the commit message to stdout."
- def __init__(self, cfg, repos, prefix_param):
- OutputBase.__init__(self, cfg, repos, prefix_param)
- self.write_binary = _stdout.write
-
def start(self, group, params):
- self.write("Group: " + (group or "defaults") + "\n")
- self.write("Subject: " + self.make_subject(group, params) + "\n\n")
+ encoding = sys.stdout.encoding if PY3 else 'utf-8'
+ writer = Writer(_stdout.write, encoding)
+
+ writer.write("Group: " + (group or "defaults") + "\n")
+ writer.write("Subject: " + self.make_subject(group, params) + "\n\n")
+
+ return writer
def finish(self):
pass
- if (PY3 and (codecs.lookup(sys.stdout.encoding) != codecs.lookup('utf-8'))):
- def write(self, output):
- """Write text as *default* encoding string"""
- return self.write_binary(output.encode(sys.stdout.encoding,
- 'backslashreplace'))
-
class PipeOutput(MailedOutput):
"Deliver a mail message to an MTA via a pipe."
@@ -427,10 +435,12 @@ class PipeOutput(MailedOutput):
# construct the pipe for talking to the mailer
self.pipe = subprocess.Popen(cmd, stdin=subprocess.PIPE,
close_fds=sys.platform != "win32")
- self.write_binary = self.pipe.stdin.write
+ writer = Writer(self.pipe.stdin.write)
# start writing out the mail message
- self.write(self.mail_headers(group, params))
+ writer.write(self.mail_headers(group, params))
+
+ return writer
def finish(self):
# signal that we're done sending content
@@ -523,10 +533,10 @@ class Commit(Messenger):
for (group, param_tuple), (params, paths) in sorted(self.groups.items()):
try:
- self.output.start(group, params)
+ writer = self.output.start(group, params)
# generate the content for this group and set of params
- generate_content(self.output, self.cfg, self.repos, self.changelist,
+ generate_content(writer, self.cfg, self.repos, self.changelist,
group, params, paths, subpool)
self.output.finish()
@@ -559,8 +569,8 @@ class PropChange(Messenger):
ret = 0
for (group, param_tuple), params in self.groups.items():
try:
- self.output.start(group, params)
- self.output.write('Author: %s\n'
+ writer = self.output.start(group, params)
+ writer.write('Author: %s\n'
'Revision: %s\n'
'Property Name: %s\n'
'Action: %s\n'
@@ -569,11 +579,11 @@ class PropChange(Messenger):
actions.get(self.action, 'Unknown (\'%s\')' \
% self.action)))
if self.action == 'A' or self.action not in actions:
- self.output.write('Property value:\n')
+ writer.write('Property value:\n')
propvalue = self.repos.get_rev_prop(self.propname)
- self.output.write(propvalue)
+ writer.write(propvalue)
elif self.action == 'M':
- self.output.write('Property diff:\n')
+ writer.write('Property diff:\n')
tempfile1 = tempfile.NamedTemporaryFile()
tempfile1.write(_stdin.read())
tempfile1.flush()
@@ -673,18 +683,18 @@ class Lock(Messenger):
ret = 0
for (group, param_tuple), (params, paths) in sorted(self.groups.items()):
try:
- self.output.start(group, params)
+ writer = self.output.start(group, params)
- self.output.write('Author: %s\n'
+ writer.write('Author: %s\n'
'%s paths:\n' %
(self.author, self.do_lock and 'Locked' or
'Unlocked'))
self.dirlist.sort()
for dir in self.dirlist:
- self.output.write(' %s\n\n' % dir)
+ writer.write(' %s\n\n' % dir)
if self.do_lock:
- self.output.write('Comment:\n%s\n' % (self.lock.comment or ''))
+ writer.write('Comment:\n%s\n' % (self.lock.comment or ''))
self.output.finish()
except MessageSendFailure:
@@ -761,7 +771,7 @@ class DiffURLSelections:
return self._get_url('modify', repos_rev, change)
-def generate_content(output, cfg, repos, changelist, group, params, paths,
+def generate_content(writer, cfg, repos, changelist, group, params, paths,
pool):
svndate = repos.get_rev_prop(svn.core.SVN_PROP_REVISION_DATE)
@@ -813,8 +823,8 @@ def generate_content(output, cfg, repos,
other_diffs=other_diffs,
)
### clean this up in future rev. Just use wb
- w = output.write
- wb = output.write_binary
+ w = writer.write
+ wb = writer.write_binary
render_commit(w, wb, data)