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)