]> jfr.im git - yt-dlp.git/blame - youtube_dl/compat.py
[rtlxl] Fix format order
[yt-dlp.git] / youtube_dl / compat.py
CommitLineData
451948b2
PH
1from __future__ import unicode_literals
2
8c25f81b
PH
3import getpass
4import os
5import subprocess
6import sys
7
8
9try:
10 import urllib.request as compat_urllib_request
11except ImportError: # Python 2
12 import urllib2 as compat_urllib_request
13
14try:
15 import urllib.error as compat_urllib_error
16except ImportError: # Python 2
17 import urllib2 as compat_urllib_error
18
19try:
20 import urllib.parse as compat_urllib_parse
21except ImportError: # Python 2
22 import urllib as compat_urllib_parse
23
24try:
25 from urllib.parse import urlparse as compat_urllib_parse_urlparse
26except ImportError: # Python 2
27 from urlparse import urlparse as compat_urllib_parse_urlparse
28
29try:
30 import urllib.parse as compat_urlparse
31except ImportError: # Python 2
32 import urlparse as compat_urlparse
33
34try:
35 import http.cookiejar as compat_cookiejar
36except ImportError: # Python 2
37 import cookielib as compat_cookiejar
38
39try:
40 import html.entities as compat_html_entities
41except ImportError: # Python 2
42 import htmlentitydefs as compat_html_entities
43
44try:
45 import html.parser as compat_html_parser
46except ImportError: # Python 2
47 import HTMLParser as compat_html_parser
48
49try:
50 import http.client as compat_http_client
51except ImportError: # Python 2
52 import httplib as compat_http_client
53
54try:
55 from urllib.error import HTTPError as compat_HTTPError
56except ImportError: # Python 2
57 from urllib2 import HTTPError as compat_HTTPError
58
59try:
60 from urllib.request import urlretrieve as compat_urlretrieve
61except ImportError: # Python 2
62 from urllib import urlretrieve as compat_urlretrieve
63
64
65try:
66 from subprocess import DEVNULL
67 compat_subprocess_get_DEVNULL = lambda: DEVNULL
68except ImportError:
69 compat_subprocess_get_DEVNULL = lambda: open(os.path.devnull, 'w')
70
71try:
72 from urllib.parse import unquote as compat_urllib_parse_unquote
73except ImportError:
74 def compat_urllib_parse_unquote(string, encoding='utf-8', errors='replace'):
75 if string == '':
76 return string
77 res = string.split('%')
78 if len(res) == 1:
79 return string
80 if encoding is None:
81 encoding = 'utf-8'
82 if errors is None:
83 errors = 'replace'
84 # pct_sequence: contiguous sequence of percent-encoded bytes, decoded
85 pct_sequence = b''
86 string = res[0]
87 for item in res[1:]:
88 try:
89 if not item:
90 raise ValueError
91 pct_sequence += item[:2].decode('hex')
92 rest = item[2:]
93 if not rest:
94 # This segment was just a single percent-encoded character.
95 # May be part of a sequence of code units, so delay decoding.
96 # (Stored in pct_sequence).
97 continue
98 except ValueError:
99 rest = '%' + item
100 # Encountered non-percent-encoded characters. Flush the current
101 # pct_sequence.
102 string += pct_sequence.decode(encoding, errors) + rest
103 pct_sequence = b''
104 if pct_sequence:
105 # Flush the final pct_sequence
106 string += pct_sequence.decode(encoding, errors)
107 return string
108
109
110try:
111 from urllib.parse import parse_qs as compat_parse_qs
112except ImportError: # Python 2
113 # HACK: The following is the correct parse_qs implementation from cpython 3's stdlib.
114 # Python 2's version is apparently totally broken
115
116 def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
117 encoding='utf-8', errors='replace'):
118 qs, _coerce_result = qs, unicode
119 pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
120 r = []
121 for name_value in pairs:
122 if not name_value and not strict_parsing:
123 continue
124 nv = name_value.split('=', 1)
125 if len(nv) != 2:
126 if strict_parsing:
127 raise ValueError("bad query field: %r" % (name_value,))
128 # Handle case of a control-name with no equal sign
129 if keep_blank_values:
130 nv.append('')
131 else:
132 continue
133 if len(nv[1]) or keep_blank_values:
134 name = nv[0].replace('+', ' ')
135 name = compat_urllib_parse_unquote(
136 name, encoding=encoding, errors=errors)
137 name = _coerce_result(name)
138 value = nv[1].replace('+', ' ')
139 value = compat_urllib_parse_unquote(
140 value, encoding=encoding, errors=errors)
141 value = _coerce_result(value)
142 r.append((name, value))
143 return r
144
145 def compat_parse_qs(qs, keep_blank_values=False, strict_parsing=False,
146 encoding='utf-8', errors='replace'):
147 parsed_result = {}
148 pairs = _parse_qsl(qs, keep_blank_values, strict_parsing,
149 encoding=encoding, errors=errors)
150 for name, value in pairs:
151 if name in parsed_result:
152 parsed_result[name].append(value)
153 else:
154 parsed_result[name] = [value]
155 return parsed_result
156
157try:
158 compat_str = unicode # Python 2
159except NameError:
160 compat_str = str
161
162try:
163 compat_chr = unichr # Python 2
164except NameError:
165 compat_chr = chr
166
167try:
168 from xml.etree.ElementTree import ParseError as compat_xml_parse_error
169except ImportError: # Python 2.6
170 from xml.parsers.expat import ExpatError as compat_xml_parse_error
171
172try:
173 from shlex import quote as shlex_quote
174except ImportError: # Python < 3.3
175 def shlex_quote(s):
176 return "'" + s.replace("'", "'\"'\"'") + "'"
177
178
179def compat_ord(c):
180 if type(c) is int: return c
181 else: return ord(c)
182
183
184if sys.version_info >= (3, 0):
185 compat_getenv = os.getenv
186 compat_expanduser = os.path.expanduser
187else:
188 # Environment variables should be decoded with filesystem encoding.
189 # Otherwise it will fail if any non-ASCII characters present (see #3854 #3217 #2918)
190
191 def compat_getenv(key, default=None):
192 from .utils import get_filesystem_encoding
193 env = os.getenv(key, default)
194 if env:
195 env = env.decode(get_filesystem_encoding())
196 return env
197
198 # HACK: The default implementations of os.path.expanduser from cpython do not decode
199 # environment variables with filesystem encoding. We will work around this by
200 # providing adjusted implementations.
201 # The following are os.path.expanduser implementations from cpython 2.7.8 stdlib
202 # for different platforms with correct environment variables decoding.
203
204 if os.name == 'posix':
205 def compat_expanduser(path):
206 """Expand ~ and ~user constructions. If user or $HOME is unknown,
207 do nothing."""
208 if not path.startswith('~'):
209 return path
210 i = path.find('/', 1)
211 if i < 0:
212 i = len(path)
213 if i == 1:
214 if 'HOME' not in os.environ:
215 import pwd
216 userhome = pwd.getpwuid(os.getuid()).pw_dir
217 else:
218 userhome = compat_getenv('HOME')
219 else:
220 import pwd
221 try:
222 pwent = pwd.getpwnam(path[1:i])
223 except KeyError:
224 return path
225 userhome = pwent.pw_dir
226 userhome = userhome.rstrip('/')
227 return (userhome + path[i:]) or '/'
228 elif os.name == 'nt' or os.name == 'ce':
229 def compat_expanduser(path):
230 """Expand ~ and ~user constructs.
231
232 If user or $HOME is unknown, do nothing."""
233 if path[:1] != '~':
234 return path
235 i, n = 1, len(path)
236 while i < n and path[i] not in '/\\':
237 i = i + 1
238
239 if 'HOME' in os.environ:
240 userhome = compat_getenv('HOME')
241 elif 'USERPROFILE' in os.environ:
242 userhome = compat_getenv('USERPROFILE')
243 elif not 'HOMEPATH' in os.environ:
244 return path
245 else:
246 try:
247 drive = compat_getenv('HOMEDRIVE')
248 except KeyError:
249 drive = ''
250 userhome = os.path.join(drive, compat_getenv('HOMEPATH'))
251
252 if i != 1: #~user
253 userhome = os.path.join(os.path.dirname(userhome), path[1:i])
254
255 return userhome + path[i:]
256 else:
257 compat_expanduser = os.path.expanduser
258
259
260if sys.version_info < (3, 0):
261 def compat_print(s):
262 from .utils import preferredencoding
263 print(s.encode(preferredencoding(), 'xmlcharrefreplace'))
264else:
265 def compat_print(s):
266 assert type(s) == type(u'')
267 print(s)
268
269
270try:
271 subprocess_check_output = subprocess.check_output
272except AttributeError:
273 def subprocess_check_output(*args, **kwargs):
274 assert 'input' not in kwargs
275 p = subprocess.Popen(*args, stdout=subprocess.PIPE, **kwargs)
276 output, _ = p.communicate()
277 ret = p.poll()
278 if ret:
279 raise subprocess.CalledProcessError(ret, p.args, output=output)
280 return output
281
282if sys.version_info < (3, 0) and sys.platform == 'win32':
283 def compat_getpass(prompt, *args, **kwargs):
284 if isinstance(prompt, compat_str):
baa70803 285 from .utils import preferredencoding
8c25f81b
PH
286 prompt = prompt.encode(preferredencoding())
287 return getpass.getpass(prompt, *args, **kwargs)
288else:
289 compat_getpass = getpass.getpass
290
c7b0add8
PH
291# Old 2.6 and 2.7 releases require kwargs to be bytes
292try:
293 (lambda x: x)(**{'x': 0})
294except TypeError:
295 def compat_kwargs(kwargs):
296 return dict((bytes(k), v) for k, v in kwargs.items())
297else:
298 compat_kwargs = lambda kwargs: kwargs
8c25f81b
PH
299
300__all__ = [
301 'compat_HTTPError',
302 'compat_chr',
303 'compat_cookiejar',
304 'compat_expanduser',
305 'compat_getenv',
306 'compat_getpass',
307 'compat_html_entities',
308 'compat_html_parser',
309 'compat_http_client',
c7b0add8 310 'compat_kwargs',
8c25f81b
PH
311 'compat_ord',
312 'compat_parse_qs',
313 'compat_print',
314 'compat_str',
315 'compat_subprocess_get_DEVNULL',
316 'compat_urllib_error',
317 'compat_urllib_parse',
318 'compat_urllib_parse_unquote',
319 'compat_urllib_parse_urlparse',
320 'compat_urllib_request',
321 'compat_urlparse',
322 'compat_urlretrieve',
323 'compat_xml_parse_error',
324 'shlex_quote',
325 'subprocess_check_output',
326]