Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
aa2d1b37
Commit
aa2d1b37
authored
Sep 09, 2016
by
R David Murray
Browse files
Options
Browse Files
Download
Plain Diff
Merge: #14977: Make mailcap respect the order of the lines in the mailcap file.
parents
91c258cf
4e830165
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
80 additions
and
27 deletions
+80
-27
Lib/mailcap.py
Lib/mailcap.py
+25
-3
Lib/test/mailcap.txt
Lib/test/mailcap.txt
+1
-1
Lib/test/test_mailcap.py
Lib/test/test_mailcap.py
+46
-23
Misc/ACKS
Misc/ACKS
+1
-0
Misc/NEWS
Misc/NEWS
+7
-0
No files found.
Lib/mailcap.py
View file @
aa2d1b37
"""Mailcap file handling. See RFC 1524."""
"""Mailcap file handling. See RFC 1524."""
import
os
import
os
import
warnings
__all__
=
[
"getcaps"
,
"findmatch"
]
__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.
# Part 1: top-level interface.
def
getcaps
():
def
getcaps
():
...
@@ -17,13 +27,14 @@ def getcaps():
...
@@ -17,13 +27,14 @@ def getcaps():
"""
"""
caps
=
{}
caps
=
{}
lineno
=
0
for
mailcap
in
listmailcapfiles
():
for
mailcap
in
listmailcapfiles
():
try
:
try
:
fp
=
open
(
mailcap
,
'r'
)
fp
=
open
(
mailcap
,
'r'
)
except
OSError
:
except
OSError
:
continue
continue
with
fp
:
with
fp
:
morecaps
=
readmailcapfile
(
fp
)
morecaps
,
lineno
=
_readmailcapfile
(
fp
,
lineno
)
for
key
,
value
in
morecaps
.
items
():
for
key
,
value
in
morecaps
.
items
():
if
not
key
in
caps
:
if
not
key
in
caps
:
caps
[
key
]
=
value
caps
[
key
]
=
value
...
@@ -49,8 +60,15 @@ def listmailcapfiles():
...
@@ -49,8 +60,15 @@ def listmailcapfiles():
# Part 2: the parser.
# Part 2: the parser.
def
readmailcapfile
(
fp
):
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
,
lineno
):
"""Read a mailcap file and return a dictionary keyed by MIME type.
"""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
Each MIME type is mapped to an entry consisting of a list of
...
@@ -76,6 +94,9 @@ def readmailcapfile(fp):
...
@@ -76,6 +94,9 @@ def readmailcapfile(fp):
key
,
fields
=
parseline
(
line
)
key
,
fields
=
parseline
(
line
)
if
not
(
key
and
fields
):
if
not
(
key
and
fields
):
continue
continue
if
lineno
is
not
None
:
fields
[
'lineno'
]
=
lineno
lineno
+=
1
# Normalize the key
# Normalize the key
types
=
key
.
split
(
'/'
)
types
=
key
.
split
(
'/'
)
for
j
in
range
(
len
(
types
)):
for
j
in
range
(
len
(
types
)):
...
@@ -86,7 +107,7 @@ def readmailcapfile(fp):
...
@@ -86,7 +107,7 @@ def readmailcapfile(fp):
caps
[
key
].
append
(
fields
)
caps
[
key
].
append
(
fields
)
else
:
else
:
caps
[
key
]
=
[
fields
]
caps
[
key
]
=
[
fields
]
return
caps
return
caps
,
lineno
def
parseline
(
line
):
def
parseline
(
line
):
"""Parse one entry in a mailcap file and return a dictionary.
"""Parse one entry in a mailcap file and return a dictionary.
...
@@ -165,6 +186,7 @@ def lookup(caps, MIMEtype, key=None):
...
@@ -165,6 +186,7 @@ def lookup(caps, MIMEtype, key=None):
entries
=
entries
+
caps
[
MIMEtype
]
entries
=
entries
+
caps
[
MIMEtype
]
if
key
is
not
None
:
if
key
is
not
None
:
entries
=
[
e
for
e
in
entries
if
key
in
e
]
entries
=
[
e
for
e
in
entries
if
key
in
e
]
entries
=
sorted
(
entries
,
key
=
lineno_sort_key
)
return
entries
return
entries
def
subst
(
field
,
MIMEtype
,
filename
,
plist
=
[]):
def
subst
(
field
,
MIMEtype
,
filename
,
plist
=
[]):
...
...
Lib/test/mailcap.txt
View file @
aa2d1b37
...
@@ -35,5 +35,5 @@ message/external-body; showexternal %s %{access-type} %{name} %{site} \
...
@@ -35,5 +35,5 @@ message/external-body; showexternal %s %{access-type} %{name} %{site} \
text/richtext; shownonascii iso-8859-8 -e richtext -p %s; test=test "`echo \
text/richtext; shownonascii iso-8859-8 -e richtext -p %s; test=test "`echo \
%{charset} | tr '[A-Z]' '[a-z]'`" = iso-8859-8; copiousoutput
%{charset} | tr '[A-Z]' '[a-z]'`" = iso-8859-8; copiousoutput
video/mpeg; mpeg_play %s
video/*; animate %s
video/*; animate %s
video/mpeg; mpeg_play %s
\ No newline at end of file
Lib/test/test_mailcap.py
View file @
aa2d1b37
import
mailcap
import
mailcap
import
os
import
os
import
copy
import
test.support
import
test.support
import
unittest
import
unittest
...
@@ -13,43 +14,55 @@ MAILCAPDICT = {
...
@@ -13,43 +14,55 @@ MAILCAPDICT = {
[{
'compose'
:
'moviemaker %s'
,
[{
'compose'
:
'moviemaker %s'
,
'x11-bitmap'
:
'"/usr/lib/Zmail/bitmaps/movie.xbm"'
,
'x11-bitmap'
:
'"/usr/lib/Zmail/bitmaps/movie.xbm"'
,
'description'
:
'"Movie"'
,
'description'
:
'"Movie"'
,
'view'
:
'movieplayer %s'
}],
'view'
:
'movieplayer %s'
,
'lineno'
:
4
}],
'application/*'
:
'application/*'
:
[{
'copiousoutput'
:
''
,
[{
'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'
:
'audio/basic'
:
[{
'edit'
:
'audiocompose %s'
,
[{
'edit'
:
'audiocompose %s'
,
'compose'
:
'audiocompose %s'
,
'compose'
:
'audiocompose %s'
,
'description'
:
'"An audio fragment"'
,
'description'
:
'"An audio fragment"'
,
'view'
:
'showaudio %s'
}],
'view'
:
'showaudio %s'
,
'lineno'
:
6
}],
'video/mpeg'
:
'video/mpeg'
:
[{
'view'
:
'mpeg_play %s'
}],
[{
'view'
:
'mpeg_play %s'
,
'lineno'
:
13
}],
'application/postscript'
:
'application/postscript'
:
[{
'needsterminal'
:
''
,
'view'
:
'ps-to-terminal %s'
},
[{
'needsterminal'
:
''
,
'view'
:
'ps-to-terminal %s'
,
'lineno'
:
1
},
{
'compose'
:
'idraw %s'
,
'view'
:
'ps-to-terminal %s'
}],
{
'compose'
:
'idraw %s'
,
'view'
:
'ps-to-terminal %s'
,
'lineno'
:
2
}],
'application/x-dvi'
:
'application/x-dvi'
:
[{
'view'
:
'xdvi %s'
}],
[{
'view'
:
'xdvi %s'
,
'lineno'
:
3
}],
'message/external-body'
:
'message/external-body'
:
[{
'composetyped'
:
'extcompose %s'
,
[{
'composetyped'
:
'extcompose %s'
,
'description'
:
'"A reference to data stored in an external location"'
,
'description'
:
'"A reference to data stored in an external location"'
,
'needsterminal'
:
''
,
'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'
:
'text/richtext'
:
[{
'test'
:
'test "`echo %{charset} | tr
\
'
[A-Z]
\
'
\
'
[a-z]
\
'
`" = iso-8859-8'
,
[{
'test'
:
'test "`echo %{charset} | tr
\
'
[A-Z]
\
'
\
'
[a-z]
\
'
`" = iso-8859-8'
,
'copiousoutput'
:
''
,
'copiousoutput'
:
''
,
'view'
:
'shownonascii iso-8859-8 -e richtext -p %s'
}],
'view'
:
'shownonascii iso-8859-8 -e richtext -p %s'
,
'lineno'
:
11
}],
'image/x-xwindowdump'
:
'image/x-xwindowdump'
:
[{
'view'
:
'display %s'
}],
[{
'view'
:
'display %s'
,
'lineno'
:
9
}],
'audio/*'
:
'audio/*'
:
[{
'view'
:
'/usr/local/bin/showaudio %t'
}],
[{
'view'
:
'/usr/local/bin/showaudio %t'
,
'lineno'
:
7
}],
'video/*'
:
'video/*'
:
[{
'view'
:
'animate %s'
}],
[{
'view'
:
'animate %s'
,
'lineno'
:
12
}],
'application/frame'
:
'application/frame'
:
[{
'print'
:
'"cat %s | lp"'
,
'view'
:
'showframe %s'
}],
[{
'print'
:
'"cat %s | lp"'
,
'view'
:
'showframe %s'
,
'lineno'
:
0
}],
'image/rgb'
:
'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
):
class
HelperFunctionTest
(
unittest
.
TestCase
):
...
@@ -75,12 +88,14 @@ class HelperFunctionTest(unittest.TestCase):
...
@@ -75,12 +88,14 @@ class HelperFunctionTest(unittest.TestCase):
def
test_readmailcapfile
(
self
):
def
test_readmailcapfile
(
self
):
# Test readmailcapfile() using test file. It should match MAILCAPDICT.
# Test readmailcapfile() using test file. It should match MAILCAPDICT.
with
open
(
MAILCAPFILE
,
'r'
)
as
mcf
:
with
open
(
MAILCAPFILE
,
'r'
)
as
mcf
:
with
self
.
assertWarns
(
DeprecationWarning
):
d
=
mailcap
.
readmailcapfile
(
mcf
)
d
=
mailcap
.
readmailcapfile
(
mcf
)
self
.
assertDictEqual
(
d
,
MAILCAPDICT
)
self
.
assertDictEqual
(
d
,
MAILCAPDICT
_DEPRECATED
)
def
test_lookup
(
self
):
def
test_lookup
(
self
):
# Test without key
# 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'
)
actual
=
mailcap
.
lookup
(
MAILCAPDICT
,
'video/mpeg'
)
self
.
assertListEqual
(
expected
,
actual
)
self
.
assertListEqual
(
expected
,
actual
)
...
@@ -89,10 +104,16 @@ class HelperFunctionTest(unittest.TestCase):
...
@@ -89,10 +104,16 @@ class HelperFunctionTest(unittest.TestCase):
expected
=
[{
'edit'
:
'audiocompose %s'
,
expected
=
[{
'edit'
:
'audiocompose %s'
,
'compose'
:
'audiocompose %s'
,
'compose'
:
'audiocompose %s'
,
'description'
:
'"An audio fragment"'
,
'description'
:
'"An audio fragment"'
,
'view'
:
'showaudio %s'
}]
'view'
:
'showaudio %s'
,
'lineno'
:
6
}]
actual
=
mailcap
.
lookup
(
MAILCAPDICT
,
'audio/basic'
,
key
)
actual
=
mailcap
.
lookup
(
MAILCAPDICT
,
'audio/basic'
,
key
)
self
.
assertListEqual
(
expected
,
actual
)
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
):
def
test_subst
(
self
):
plist
=
[
'id=1'
,
'number=2'
,
'total=3'
]
plist
=
[
'id=1'
,
'number=2'
,
'total=3'
]
# test case: ([field, MIMEtype, filename, plist=[]], <expected string>)
# test case: ([field, MIMEtype, filename, plist=[]], <expected string>)
...
@@ -151,14 +172,16 @@ class FindmatchTest(unittest.TestCase):
...
@@ -151,14 +172,16 @@ class FindmatchTest(unittest.TestCase):
'edit'
:
'audiocompose %s'
,
'edit'
:
'audiocompose %s'
,
'compose'
:
'audiocompose %s'
,
'compose'
:
'audiocompose %s'
,
'description'
:
'"An audio fragment"'
,
'description'
:
'"An audio fragment"'
,
'view'
:
'showaudio %s'
'view'
:
'showaudio %s'
,
'lineno'
:
6
}
}
audio_entry
=
{
"view"
:
"/usr/local/bin/showaudio %t"
}
audio_entry
=
{
"view"
:
"/usr/local/bin/showaudio %t"
,
'lineno'
:
7
}
video_entry
=
{
'view'
:
'animate %s'
}
video_entry
=
{
'view'
:
'animate %s'
,
'lineno'
:
12
}
message_entry
=
{
message_entry
=
{
'composetyped'
:
'extcompose %s'
,
'composetyped'
:
'extcompose %s'
,
'description'
:
'"A reference to data stored in an external location"'
,
'needsterminal'
:
''
,
'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)
# test case: (findmatch args, findmatch keyword args, expected output)
...
@@ -168,7 +191,7 @@ class FindmatchTest(unittest.TestCase):
...
@@ -168,7 +191,7 @@ class FindmatchTest(unittest.TestCase):
cases
=
[
cases
=
[
([{},
"video/mpeg"
],
{},
(
None
,
None
)),
([{},
"video/mpeg"
],
{},
(
None
,
None
)),
([
c
,
"foo/bar"
],
{},
(
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"
,
"edit"
],
{},
(
"audiocompose /dev/null"
,
audio_basic_entry
)),
([
c
,
"audio/basic"
,
"compose"
],
{},
(
"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
)),
([
c
,
"audio/basic"
,
"description"
],
{},
(
'"An audio fragment"'
,
audio_basic_entry
)),
...
...
Misc/ACKS
View file @
aa2d1b37
...
@@ -843,6 +843,7 @@ Julia Lawall
...
@@ -843,6 +843,7 @@ Julia Lawall
Chris Lawrence
Chris Lawrence
Mark Lawrence
Mark Lawrence
Chris Laws
Chris Laws
Michael Lazar
Brian Leair
Brian Leair
Mathieu Leduc-Hamel
Mathieu Leduc-Hamel
Amandine Lee
Amandine Lee
...
...
Misc/NEWS
View file @
aa2d1b37
...
@@ -122,6 +122,9 @@ Core and Builtins
...
@@ -122,6 +122,9 @@ Core and Builtins
Library
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
.
-
Issue
#
28025
:
Convert
all
ssl
module
constants
to
IntEnum
and
IntFlags
.
SSLContext
properties
now
return
flags
and
enums
.
SSLContext
properties
now
return
flags
and
enums
.
...
@@ -145,6 +148,10 @@ Library
...
@@ -145,6 +148,10 @@ Library
- Issue #24277: The new email API is no longer provisional, and the docs
- Issue #24277: The new email API is no longer provisional, and the docs
have been reorganized and rewritten to emphasize the new API.
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
- lib2to3.pgen3.driver.load_grammar() now creates a stable cache file
between runs given the same Grammar.txt input regardless of the hash
between runs given the same Grammar.txt input regardless of the hash
randomization setting.
randomization setting.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment