[Python-checkins] cpython (merge 3.5 -> default): Merge: #14977: Make mailcap respect the order of the lines in the mailcap file.

r.david.murray python-checkins at python.org
Fri Sep 9 20:09:58 EDT 2016


https://hg.python.org/cpython/rev/efd692c86429
changeset:   103507:efd692c86429
parent:      103505:c89692446258
parent:      103506:f1bf0abcca0c
user:        R David Murray <rdmurray at bitdance.com>
date:        Fri Sep 09 20:09:43 2016 -0400
summary:
  Merge: #14977: Make mailcap respect the order of the lines in the mailcap file.

files:
  Lib/mailcap.py           |  28 ++++++++++-
  Lib/test/mailcap.txt     |   2 +-
  Lib/test/test_mailcap.py |  69 ++++++++++++++++++---------
  Misc/ACKS                |   1 +
  Misc/NEWS                |   7 ++
  5 files changed, 80 insertions(+), 27 deletions(-)


diff --git a/Lib/mailcap.py b/Lib/mailcap.py
--- a/Lib/mailcap.py
+++ b/Lib/mailcap.py
@@ -1,9 +1,19 @@
 """Mailcap file handling.  See RFC 1524."""
 
 import os
+import warnings
 
 __all__ = ["getcaps","findmatch"]
 
+
+def lineno_sort_key(entry):
+    # Sort in ascending order, with unspecified entries at the end
+    if 'lineno' in entry:
+        return 0, entry['lineno']
+    else:
+        return 1, 0
+
+
 # Part 1: top-level interface.
 
 def getcaps():
@@ -17,13 +27,14 @@
 
     """
     caps = {}
+    lineno = 0
     for mailcap in listmailcapfiles():
         try:
             fp = open(mailcap, 'r')
         except OSError:
             continue
         with fp:
-            morecaps = readmailcapfile(fp)
+            morecaps, lineno = _readmailcapfile(fp, lineno)
         for key, value in morecaps.items():
             if not key in caps:
                 caps[key] = value
@@ -49,8 +60,15 @@
 
 
 # Part 2: the parser.
+def readmailcapfile(fp):
+    """Read a mailcap file and return a dictionary keyed by MIME type."""
+    warnings.warn('readmailcapfile is deprecated, use getcaps instead',
+                  DeprecationWarning, 2)
+    caps, _ = _readmailcapfile(fp, None)
+    return caps
 
-def readmailcapfile(fp):
+
+def _readmailcapfile(fp, lineno):
     """Read a mailcap file and return a dictionary keyed by MIME type.
 
     Each MIME type is mapped to an entry consisting of a list of
@@ -76,6 +94,9 @@
         key, fields = parseline(line)
         if not (key and fields):
             continue
+        if lineno is not None:
+            fields['lineno'] = lineno
+            lineno += 1
         # Normalize the key
         types = key.split('/')
         for j in range(len(types)):
@@ -86,7 +107,7 @@
             caps[key].append(fields)
         else:
             caps[key] = [fields]
-    return caps
+    return caps, lineno
 
 def parseline(line):
     """Parse one entry in a mailcap file and return a dictionary.
@@ -165,6 +186,7 @@
         entries = entries + caps[MIMEtype]
     if key is not None:
         entries = [e for e in entries if key in e]
+    entries = sorted(entries, key=lineno_sort_key)
     return entries
 
 def subst(field, MIMEtype, filename, plist=[]):
diff --git a/Lib/test/mailcap.txt b/Lib/test/mailcap.txt
--- a/Lib/test/mailcap.txt
+++ b/Lib/test/mailcap.txt
@@ -35,5 +35,5 @@
 text/richtext; shownonascii iso-8859-8 -e richtext -p %s; test=test "`echo \
     %{charset} | tr '[A-Z]' '[a-z]'`"  = iso-8859-8; copiousoutput
 
-video/mpeg; mpeg_play %s
 video/*; animate %s
+video/mpeg; mpeg_play %s
\ No newline at end of file
diff --git a/Lib/test/test_mailcap.py b/Lib/test/test_mailcap.py
--- a/Lib/test/test_mailcap.py
+++ b/Lib/test/test_mailcap.py
@@ -1,5 +1,6 @@
 import mailcap
 import os
+import copy
 import test.support
 import unittest
 
@@ -13,43 +14,55 @@
         [{'compose': 'moviemaker %s',
           'x11-bitmap': '"/usr/lib/Zmail/bitmaps/movie.xbm"',
           'description': '"Movie"',
-          'view': 'movieplayer %s'}],
+          'view': 'movieplayer %s',
+          'lineno': 4}],
     'application/*':
         [{'copiousoutput': '',
-          'view': 'echo "This is \\"%t\\" but        is 50 \\% Greek to me" \\; cat %s'}],
+          'view': 'echo "This is \\"%t\\" but        is 50 \\% Greek to me" \\; cat %s',
+          'lineno': 5}],
     'audio/basic':
         [{'edit': 'audiocompose %s',
           'compose': 'audiocompose %s',
           'description': '"An audio fragment"',
-          'view': 'showaudio %s'}],
+          'view': 'showaudio %s',
+          'lineno': 6}],
     'video/mpeg':
-        [{'view': 'mpeg_play %s'}],
+        [{'view': 'mpeg_play %s', 'lineno': 13}],
     'application/postscript':
-        [{'needsterminal': '', 'view': 'ps-to-terminal %s'},
-         {'compose': 'idraw %s', 'view': 'ps-to-terminal %s'}],
+        [{'needsterminal': '', 'view': 'ps-to-terminal %s', 'lineno': 1},
+         {'compose': 'idraw %s', 'view': 'ps-to-terminal %s', 'lineno': 2}],
     'application/x-dvi':
-        [{'view': 'xdvi %s'}],
+        [{'view': 'xdvi %s', 'lineno': 3}],
     'message/external-body':
         [{'composetyped': 'extcompose %s',
           'description': '"A reference to data stored in an external location"',
           'needsterminal': '',
-          'view': 'showexternal %s %{access-type} %{name} %{site}     %{directory} %{mode} %{server}'}],
+          'view': 'showexternal %s %{access-type} %{name} %{site}     %{directory} %{mode} %{server}',
+          'lineno': 10}],
     'text/richtext':
         [{'test': 'test "`echo     %{charset} | tr \'[A-Z]\' \'[a-z]\'`"  = iso-8859-8',
           'copiousoutput': '',
-          'view': 'shownonascii iso-8859-8 -e richtext -p %s'}],
+          'view': 'shownonascii iso-8859-8 -e richtext -p %s',
+          'lineno': 11}],
     'image/x-xwindowdump':
-        [{'view': 'display %s'}],
+        [{'view': 'display %s', 'lineno': 9}],
     'audio/*':
-        [{'view': '/usr/local/bin/showaudio %t'}],
+        [{'view': '/usr/local/bin/showaudio %t', 'lineno': 7}],
     'video/*':
-        [{'view': 'animate %s'}],
+        [{'view': 'animate %s', 'lineno': 12}],
     'application/frame':
-        [{'print': '"cat %s | lp"', 'view': 'showframe %s'}],
+        [{'print': '"cat %s | lp"', 'view': 'showframe %s', 'lineno': 0}],
     'image/rgb':
-        [{'view': 'display %s'}]
+        [{'view': 'display %s', 'lineno': 8}]
 }
 
+# For backwards compatibility, readmailcapfile() and lookup() still support
+# the old version of mailcapdict without line numbers.
+MAILCAPDICT_DEPRECATED = copy.deepcopy(MAILCAPDICT)
+for entry_list in MAILCAPDICT_DEPRECATED.values():
+    for entry in entry_list:
+        entry.pop('lineno')
+
 
 class HelperFunctionTest(unittest.TestCase):
 
@@ -75,12 +88,14 @@
     def test_readmailcapfile(self):
         # Test readmailcapfile() using test file. It should match MAILCAPDICT.
         with open(MAILCAPFILE, 'r') as mcf:
-            d = mailcap.readmailcapfile(mcf)
-        self.assertDictEqual(d, MAILCAPDICT)
+            with self.assertWarns(DeprecationWarning):
+                d = mailcap.readmailcapfile(mcf)
+        self.assertDictEqual(d, MAILCAPDICT_DEPRECATED)
 
     def test_lookup(self):
         # Test without key
-        expected = [{'view': 'mpeg_play %s'}, {'view': 'animate %s'}]
+        expected = [{'view': 'animate %s', 'lineno': 12},
+                    {'view': 'mpeg_play %s', 'lineno': 13}]
         actual = mailcap.lookup(MAILCAPDICT, 'video/mpeg')
         self.assertListEqual(expected, actual)
 
@@ -89,10 +104,16 @@
         expected = [{'edit': 'audiocompose %s',
                      'compose': 'audiocompose %s',
                      'description': '"An audio fragment"',
-                     'view': 'showaudio %s'}]
+                     'view': 'showaudio %s',
+                     'lineno': 6}]
         actual = mailcap.lookup(MAILCAPDICT, 'audio/basic', key)
         self.assertListEqual(expected, actual)
 
+        # Test on user-defined dicts without line numbers
+        expected = [{'view': 'mpeg_play %s'}, {'view': 'animate %s'}]
+        actual = mailcap.lookup(MAILCAPDICT_DEPRECATED, 'video/mpeg')
+        self.assertListEqual(expected, actual)
+
     def test_subst(self):
         plist = ['id=1', 'number=2', 'total=3']
         # test case: ([field, MIMEtype, filename, plist=[]], <expected string>)
@@ -151,14 +172,16 @@
             'edit': 'audiocompose %s',
             'compose': 'audiocompose %s',
             'description': '"An audio fragment"',
-            'view': 'showaudio %s'
+            'view': 'showaudio %s',
+            'lineno': 6
         }
-        audio_entry = {"view": "/usr/local/bin/showaudio %t"}
-        video_entry = {'view': 'animate %s'}
+        audio_entry = {"view": "/usr/local/bin/showaudio %t", 'lineno': 7}
+        video_entry = {'view': 'animate %s', 'lineno': 12}
         message_entry = {
             'composetyped': 'extcompose %s',
             'description': '"A reference to data stored in an external location"', 'needsterminal': '',
-            'view': 'showexternal %s %{access-type} %{name} %{site}     %{directory} %{mode} %{server}'
+            'view': 'showexternal %s %{access-type} %{name} %{site}     %{directory} %{mode} %{server}',
+            'lineno': 10,
         }
 
         # test case: (findmatch args, findmatch keyword args, expected output)
@@ -168,7 +191,7 @@
         cases = [
             ([{}, "video/mpeg"], {}, (None, None)),
             ([c, "foo/bar"], {}, (None, None)),
-            ([c, "video/mpeg"], {}, ('mpeg_play /dev/null', {'view': 'mpeg_play %s'})),
+            ([c, "video/mpeg"], {}, ('animate /dev/null', video_entry)),
             ([c, "audio/basic", "edit"], {}, ("audiocompose /dev/null", audio_basic_entry)),
             ([c, "audio/basic", "compose"], {}, ("audiocompose /dev/null", audio_basic_entry)),
             ([c, "audio/basic", "description"], {}, ('"An audio fragment"', audio_basic_entry)),
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -843,6 +843,7 @@
 Chris Lawrence
 Mark Lawrence
 Chris Laws
+Michael Lazar
 Brian Leair
 Mathieu Leduc-Hamel
 Amandine Lee
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -122,6 +122,9 @@
 Library
 -------
 
+- Issue #14977: mailcap now respects the order of the lines in the mailcap
+  files ("first match"), as required by RFC 1542.  Patch by Michael Lazar.
+
 - Issue #28025: Convert all ssl module constants to IntEnum and IntFlags.
   SSLContext properties now return flags and enums.
 
@@ -145,6 +148,10 @@
 - Issue #24277: The new email API is no longer provisional, and the docs
   have been reorganized and rewritten to emphasize the new API.
 
+- Issue #22450: urllib now includes an "Accept: */*" header among the
+  default headers.  This makes the results of REST API requests more
+  consistent and predictable especially when proxy servers are involved.
+
 - lib2to3.pgen3.driver.load_grammar() now creates a stable cache file
   between runs given the same Grammar.txt input regardless of the hash
   randomization setting.

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list