##// END OF EJS Templates
py3: convert __doc__ back to bytes in help.py...
Yuya Nishihara -
r32615:c9318beb default
parent child Browse files
Show More
@@ -1,667 +1,668 b''
1 # help.py - help data for mercurial
1 # help.py - help data for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12 import textwrap
12 import textwrap
13
13
14 from .i18n import (
14 from .i18n import (
15 _,
15 _,
16 gettext,
16 gettext,
17 )
17 )
18 from . import (
18 from . import (
19 cmdutil,
19 cmdutil,
20 encoding,
20 encoding,
21 error,
21 error,
22 extensions,
22 extensions,
23 filemerge,
23 filemerge,
24 fileset,
24 fileset,
25 minirst,
25 minirst,
26 pycompat,
26 pycompat,
27 revset,
27 revset,
28 templatefilters,
28 templatefilters,
29 templatekw,
29 templatekw,
30 templater,
30 templater,
31 util,
31 util,
32 )
32 )
33 from .hgweb import (
33 from .hgweb import (
34 webcommands,
34 webcommands,
35 )
35 )
36
36
37 _exclkeywords = {
37 _exclkeywords = {
38 "(ADVANCED)",
38 "(ADVANCED)",
39 "(DEPRECATED)",
39 "(DEPRECATED)",
40 "(EXPERIMENTAL)",
40 "(EXPERIMENTAL)",
41 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
41 # i18n: "(ADVANCED)" is a keyword, must be translated consistently
42 _("(ADVANCED)"),
42 _("(ADVANCED)"),
43 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
43 # i18n: "(DEPRECATED)" is a keyword, must be translated consistently
44 _("(DEPRECATED)"),
44 _("(DEPRECATED)"),
45 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
45 # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently
46 _("(EXPERIMENTAL)"),
46 _("(EXPERIMENTAL)"),
47 }
47 }
48
48
49 def listexts(header, exts, indent=1, showdeprecated=False):
49 def listexts(header, exts, indent=1, showdeprecated=False):
50 '''return a text listing of the given extensions'''
50 '''return a text listing of the given extensions'''
51 rst = []
51 rst = []
52 if exts:
52 if exts:
53 for name, desc in sorted(exts.iteritems()):
53 for name, desc in sorted(exts.iteritems()):
54 if not showdeprecated and any(w in desc for w in _exclkeywords):
54 if not showdeprecated and any(w in desc for w in _exclkeywords):
55 continue
55 continue
56 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
56 rst.append('%s:%s: %s\n' % (' ' * indent, name, desc))
57 if rst:
57 if rst:
58 rst.insert(0, '\n%s\n\n' % header)
58 rst.insert(0, '\n%s\n\n' % header)
59 return rst
59 return rst
60
60
61 def extshelp(ui):
61 def extshelp(ui):
62 rst = loaddoc('extensions')(ui).splitlines(True)
62 rst = loaddoc('extensions')(ui).splitlines(True)
63 rst.extend(listexts(
63 rst.extend(listexts(
64 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
64 _('enabled extensions:'), extensions.enabled(), showdeprecated=True))
65 rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
65 rst.extend(listexts(_('disabled extensions:'), extensions.disabled()))
66 doc = ''.join(rst)
66 doc = ''.join(rst)
67 return doc
67 return doc
68
68
69 def optrst(header, options, verbose):
69 def optrst(header, options, verbose):
70 data = []
70 data = []
71 multioccur = False
71 multioccur = False
72 for option in options:
72 for option in options:
73 if len(option) == 5:
73 if len(option) == 5:
74 shortopt, longopt, default, desc, optlabel = option
74 shortopt, longopt, default, desc, optlabel = option
75 else:
75 else:
76 shortopt, longopt, default, desc = option
76 shortopt, longopt, default, desc = option
77 optlabel = _("VALUE") # default label
77 optlabel = _("VALUE") # default label
78
78
79 if not verbose and any(w in desc for w in _exclkeywords):
79 if not verbose and any(w in desc for w in _exclkeywords):
80 continue
80 continue
81
81
82 so = ''
82 so = ''
83 if shortopt:
83 if shortopt:
84 so = '-' + shortopt
84 so = '-' + shortopt
85 lo = '--' + longopt
85 lo = '--' + longopt
86 if default:
86 if default:
87 desc += _(" (default: %s)") % default
87 desc += _(" (default: %s)") % default
88
88
89 if isinstance(default, list):
89 if isinstance(default, list):
90 lo += " %s [+]" % optlabel
90 lo += " %s [+]" % optlabel
91 multioccur = True
91 multioccur = True
92 elif (default is not None) and not isinstance(default, bool):
92 elif (default is not None) and not isinstance(default, bool):
93 lo += " %s" % optlabel
93 lo += " %s" % optlabel
94
94
95 data.append((so, lo, desc))
95 data.append((so, lo, desc))
96
96
97 if multioccur:
97 if multioccur:
98 header += (_(" ([+] can be repeated)"))
98 header += (_(" ([+] can be repeated)"))
99
99
100 rst = ['\n%s:\n\n' % header]
100 rst = ['\n%s:\n\n' % header]
101 rst.extend(minirst.maketable(data, 1))
101 rst.extend(minirst.maketable(data, 1))
102
102
103 return ''.join(rst)
103 return ''.join(rst)
104
104
105 def indicateomitted(rst, omitted, notomitted=None):
105 def indicateomitted(rst, omitted, notomitted=None):
106 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
106 rst.append('\n\n.. container:: omitted\n\n %s\n\n' % omitted)
107 if notomitted:
107 if notomitted:
108 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
108 rst.append('\n\n.. container:: notomitted\n\n %s\n\n' % notomitted)
109
109
110 def filtercmd(ui, cmd, kw, doc):
110 def filtercmd(ui, cmd, kw, doc):
111 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
111 if not ui.debugflag and cmd.startswith("debug") and kw != "debug":
112 return True
112 return True
113 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
113 if not ui.verbose and doc and any(w in doc for w in _exclkeywords):
114 return True
114 return True
115 return False
115 return False
116
116
117 def topicmatch(ui, commands, kw):
117 def topicmatch(ui, commands, kw):
118 """Return help topics matching kw.
118 """Return help topics matching kw.
119
119
120 Returns {'section': [(name, summary), ...], ...} where section is
120 Returns {'section': [(name, summary), ...], ...} where section is
121 one of topics, commands, extensions, or extensioncommands.
121 one of topics, commands, extensions, or extensioncommands.
122 """
122 """
123 kw = encoding.lower(kw)
123 kw = encoding.lower(kw)
124 def lowercontains(container):
124 def lowercontains(container):
125 return kw in encoding.lower(container) # translated in helptable
125 return kw in encoding.lower(container) # translated in helptable
126 results = {'topics': [],
126 results = {'topics': [],
127 'commands': [],
127 'commands': [],
128 'extensions': [],
128 'extensions': [],
129 'extensioncommands': [],
129 'extensioncommands': [],
130 }
130 }
131 for names, header, doc in helptable:
131 for names, header, doc in helptable:
132 # Old extensions may use a str as doc.
132 # Old extensions may use a str as doc.
133 if (sum(map(lowercontains, names))
133 if (sum(map(lowercontains, names))
134 or lowercontains(header)
134 or lowercontains(header)
135 or (callable(doc) and lowercontains(doc(ui)))):
135 or (callable(doc) and lowercontains(doc(ui)))):
136 results['topics'].append((names[0], header))
136 results['topics'].append((names[0], header))
137 for cmd, entry in commands.table.iteritems():
137 for cmd, entry in commands.table.iteritems():
138 if len(entry) == 3:
138 if len(entry) == 3:
139 summary = entry[2]
139 summary = entry[2]
140 else:
140 else:
141 summary = ''
141 summary = ''
142 # translate docs *before* searching there
142 # translate docs *before* searching there
143 docs = _(getattr(entry[0], '__doc__', None)) or ''
143 docs = _(pycompat.getdoc(entry[0])) or ''
144 if kw in cmd or lowercontains(summary) or lowercontains(docs):
144 if kw in cmd or lowercontains(summary) or lowercontains(docs):
145 doclines = docs.splitlines()
145 doclines = docs.splitlines()
146 if doclines:
146 if doclines:
147 summary = doclines[0]
147 summary = doclines[0]
148 cmdname = cmd.partition('|')[0].lstrip('^')
148 cmdname = cmd.partition('|')[0].lstrip('^')
149 if filtercmd(ui, cmdname, kw, docs):
149 if filtercmd(ui, cmdname, kw, docs):
150 continue
150 continue
151 results['commands'].append((cmdname, summary))
151 results['commands'].append((cmdname, summary))
152 for name, docs in itertools.chain(
152 for name, docs in itertools.chain(
153 extensions.enabled(False).iteritems(),
153 extensions.enabled(False).iteritems(),
154 extensions.disabled().iteritems()):
154 extensions.disabled().iteritems()):
155 if not docs:
155 if not docs:
156 continue
156 continue
157 mod = extensions.load(ui, name, '')
157 mod = extensions.load(ui, name, '')
158 name = name.rpartition('.')[-1]
158 name = name.rpartition('.')[-1]
159 if lowercontains(name) or lowercontains(docs):
159 if lowercontains(name) or lowercontains(docs):
160 # extension docs are already translated
160 # extension docs are already translated
161 results['extensions'].append((name, docs.splitlines()[0]))
161 results['extensions'].append((name, docs.splitlines()[0]))
162 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
162 for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
163 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
163 if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
164 cmdname = cmd.partition('|')[0].lstrip('^')
164 cmdname = cmd.partition('|')[0].lstrip('^')
165 if entry[0].__doc__:
165 cmddoc = pycompat.getdoc(entry[0])
166 cmddoc = gettext(entry[0].__doc__).splitlines()[0]
166 if cmddoc:
167 cmddoc = gettext(cmddoc).splitlines()[0]
167 else:
168 else:
168 cmddoc = _('(no help text available)')
169 cmddoc = _('(no help text available)')
169 if filtercmd(ui, cmdname, kw, cmddoc):
170 if filtercmd(ui, cmdname, kw, cmddoc):
170 continue
171 continue
171 results['extensioncommands'].append((cmdname, cmddoc))
172 results['extensioncommands'].append((cmdname, cmddoc))
172 return results
173 return results
173
174
174 def loaddoc(topic, subdir=None):
175 def loaddoc(topic, subdir=None):
175 """Return a delayed loader for help/topic.txt."""
176 """Return a delayed loader for help/topic.txt."""
176
177
177 def loader(ui):
178 def loader(ui):
178 docdir = os.path.join(util.datapath, 'help')
179 docdir = os.path.join(util.datapath, 'help')
179 if subdir:
180 if subdir:
180 docdir = os.path.join(docdir, subdir)
181 docdir = os.path.join(docdir, subdir)
181 path = os.path.join(docdir, topic + ".txt")
182 path = os.path.join(docdir, topic + ".txt")
182 doc = gettext(util.readfile(path))
183 doc = gettext(util.readfile(path))
183 for rewriter in helphooks.get(topic, []):
184 for rewriter in helphooks.get(topic, []):
184 doc = rewriter(ui, topic, doc)
185 doc = rewriter(ui, topic, doc)
185 return doc
186 return doc
186
187
187 return loader
188 return loader
188
189
189 internalstable = sorted([
190 internalstable = sorted([
190 (['bundles'], _('Bundles'),
191 (['bundles'], _('Bundles'),
191 loaddoc('bundles', subdir='internals')),
192 loaddoc('bundles', subdir='internals')),
192 (['censor'], _('Censor'),
193 (['censor'], _('Censor'),
193 loaddoc('censor', subdir='internals')),
194 loaddoc('censor', subdir='internals')),
194 (['changegroups'], _('Changegroups'),
195 (['changegroups'], _('Changegroups'),
195 loaddoc('changegroups', subdir='internals')),
196 loaddoc('changegroups', subdir='internals')),
196 (['requirements'], _('Repository Requirements'),
197 (['requirements'], _('Repository Requirements'),
197 loaddoc('requirements', subdir='internals')),
198 loaddoc('requirements', subdir='internals')),
198 (['revlogs'], _('Revision Logs'),
199 (['revlogs'], _('Revision Logs'),
199 loaddoc('revlogs', subdir='internals')),
200 loaddoc('revlogs', subdir='internals')),
200 (['wireprotocol'], _('Wire Protocol'),
201 (['wireprotocol'], _('Wire Protocol'),
201 loaddoc('wireprotocol', subdir='internals')),
202 loaddoc('wireprotocol', subdir='internals')),
202 ])
203 ])
203
204
204 def internalshelp(ui):
205 def internalshelp(ui):
205 """Generate the index for the "internals" topic."""
206 """Generate the index for the "internals" topic."""
206 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
207 lines = ['To access a subtopic, use "hg help internals.{subtopic-name}"\n',
207 '\n']
208 '\n']
208 for names, header, doc in internalstable:
209 for names, header, doc in internalstable:
209 lines.append(' :%s: %s\n' % (names[0], header))
210 lines.append(' :%s: %s\n' % (names[0], header))
210
211
211 return ''.join(lines)
212 return ''.join(lines)
212
213
213 helptable = sorted([
214 helptable = sorted([
214 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
215 (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
215 (['color'], _("Colorizing Outputs"), loaddoc('color')),
216 (['color'], _("Colorizing Outputs"), loaddoc('color')),
216 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
217 (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
217 (["dates"], _("Date Formats"), loaddoc('dates')),
218 (["dates"], _("Date Formats"), loaddoc('dates')),
218 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
219 (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
219 (['environment', 'env'], _('Environment Variables'),
220 (['environment', 'env'], _('Environment Variables'),
220 loaddoc('environment')),
221 loaddoc('environment')),
221 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
222 (['revisions', 'revs', 'revsets', 'revset', 'multirevs', 'mrevs'],
222 _('Specifying Revisions'), loaddoc('revisions')),
223 _('Specifying Revisions'), loaddoc('revisions')),
223 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
224 (['filesets', 'fileset'], _("Specifying File Sets"), loaddoc('filesets')),
224 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
225 (['diffs'], _('Diff Formats'), loaddoc('diffs')),
225 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
226 (['merge-tools', 'mergetools', 'mergetool'], _('Merge Tools'),
226 loaddoc('merge-tools')),
227 loaddoc('merge-tools')),
227 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
228 (['templating', 'templates', 'template', 'style'], _('Template Usage'),
228 loaddoc('templates')),
229 loaddoc('templates')),
229 (['urls'], _('URL Paths'), loaddoc('urls')),
230 (['urls'], _('URL Paths'), loaddoc('urls')),
230 (["extensions"], _("Using Additional Features"), extshelp),
231 (["extensions"], _("Using Additional Features"), extshelp),
231 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
232 (["subrepos", "subrepo"], _("Subrepositories"), loaddoc('subrepos')),
232 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
233 (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')),
233 (["glossary"], _("Glossary"), loaddoc('glossary')),
234 (["glossary"], _("Glossary"), loaddoc('glossary')),
234 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
235 (["hgignore", "ignore"], _("Syntax for Mercurial Ignore Files"),
235 loaddoc('hgignore')),
236 loaddoc('hgignore')),
236 (["phases"], _("Working with Phases"), loaddoc('phases')),
237 (["phases"], _("Working with Phases"), loaddoc('phases')),
237 (['scripting'], _('Using Mercurial from scripts and automation'),
238 (['scripting'], _('Using Mercurial from scripts and automation'),
238 loaddoc('scripting')),
239 loaddoc('scripting')),
239 (['internals'], _("Technical implementation topics"),
240 (['internals'], _("Technical implementation topics"),
240 internalshelp),
241 internalshelp),
241 (['pager'], _("Pager Support"), loaddoc('pager')),
242 (['pager'], _("Pager Support"), loaddoc('pager')),
242 ])
243 ])
243
244
244 # Maps topics with sub-topics to a list of their sub-topics.
245 # Maps topics with sub-topics to a list of their sub-topics.
245 subtopics = {
246 subtopics = {
246 'internals': internalstable,
247 'internals': internalstable,
247 }
248 }
248
249
249 # Map topics to lists of callable taking the current topic help and
250 # Map topics to lists of callable taking the current topic help and
250 # returning the updated version
251 # returning the updated version
251 helphooks = {}
252 helphooks = {}
252
253
253 def addtopichook(topic, rewriter):
254 def addtopichook(topic, rewriter):
254 helphooks.setdefault(topic, []).append(rewriter)
255 helphooks.setdefault(topic, []).append(rewriter)
255
256
256 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
257 def makeitemsdoc(ui, topic, doc, marker, items, dedent=False):
257 """Extract docstring from the items key to function mapping, build a
258 """Extract docstring from the items key to function mapping, build a
258 single documentation block and use it to overwrite the marker in doc.
259 single documentation block and use it to overwrite the marker in doc.
259 """
260 """
260 entries = []
261 entries = []
261 for name in sorted(items):
262 for name in sorted(items):
262 text = (items[name].__doc__ or '').rstrip()
263 text = (pycompat.getdoc(items[name]) or '').rstrip()
263 if (not text
264 if (not text
264 or not ui.verbose and any(w in text for w in _exclkeywords)):
265 or not ui.verbose and any(w in text for w in _exclkeywords)):
265 continue
266 continue
266 text = gettext(text)
267 text = gettext(text)
267 if dedent:
268 if dedent:
268 # Abuse latin1 to use textwrap.dedent() on bytes.
269 # Abuse latin1 to use textwrap.dedent() on bytes.
269 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
270 text = textwrap.dedent(text.decode('latin1')).encode('latin1')
270 lines = text.splitlines()
271 lines = text.splitlines()
271 doclines = [(lines[0])]
272 doclines = [(lines[0])]
272 for l in lines[1:]:
273 for l in lines[1:]:
273 # Stop once we find some Python doctest
274 # Stop once we find some Python doctest
274 if l.strip().startswith('>>>'):
275 if l.strip().startswith('>>>'):
275 break
276 break
276 if dedent:
277 if dedent:
277 doclines.append(l.rstrip())
278 doclines.append(l.rstrip())
278 else:
279 else:
279 doclines.append(' ' + l.strip())
280 doclines.append(' ' + l.strip())
280 entries.append('\n'.join(doclines))
281 entries.append('\n'.join(doclines))
281 entries = '\n\n'.join(entries)
282 entries = '\n\n'.join(entries)
282 return doc.replace(marker, entries)
283 return doc.replace(marker, entries)
283
284
284 def addtopicsymbols(topic, marker, symbols, dedent=False):
285 def addtopicsymbols(topic, marker, symbols, dedent=False):
285 def add(ui, topic, doc):
286 def add(ui, topic, doc):
286 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
287 return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent)
287 addtopichook(topic, add)
288 addtopichook(topic, add)
288
289
289 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
290 addtopicsymbols('bundlespec', '.. bundlecompressionmarker',
290 util.bundlecompressiontopics())
291 util.bundlecompressiontopics())
291 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
292 addtopicsymbols('filesets', '.. predicatesmarker', fileset.symbols)
292 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
293 addtopicsymbols('merge-tools', '.. internaltoolsmarker',
293 filemerge.internalsdoc)
294 filemerge.internalsdoc)
294 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
295 addtopicsymbols('revisions', '.. predicatesmarker', revset.symbols)
295 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
296 addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords)
296 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
297 addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
297 addtopicsymbols('templates', '.. functionsmarker', templater.funcs)
298 addtopicsymbols('templates', '.. functionsmarker', templater.funcs)
298 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
299 addtopicsymbols('hgweb', '.. webcommandsmarker', webcommands.commands,
299 dedent=True)
300 dedent=True)
300
301
301 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
302 def help_(ui, commands, name, unknowncmd=False, full=True, subtopic=None,
302 **opts):
303 **opts):
303 '''
304 '''
304 Generate the help for 'name' as unformatted restructured text. If
305 Generate the help for 'name' as unformatted restructured text. If
305 'name' is None, describe the commands available.
306 'name' is None, describe the commands available.
306 '''
307 '''
307
308
308 opts = pycompat.byteskwargs(opts)
309 opts = pycompat.byteskwargs(opts)
309
310
310 def helpcmd(name, subtopic=None):
311 def helpcmd(name, subtopic=None):
311 try:
312 try:
312 aliases, entry = cmdutil.findcmd(name, commands.table,
313 aliases, entry = cmdutil.findcmd(name, commands.table,
313 strict=unknowncmd)
314 strict=unknowncmd)
314 except error.AmbiguousCommand as inst:
315 except error.AmbiguousCommand as inst:
315 # py3k fix: except vars can't be used outside the scope of the
316 # py3k fix: except vars can't be used outside the scope of the
316 # except block, nor can be used inside a lambda. python issue4617
317 # except block, nor can be used inside a lambda. python issue4617
317 prefix = inst.args[0]
318 prefix = inst.args[0]
318 select = lambda c: c.lstrip('^').startswith(prefix)
319 select = lambda c: c.lstrip('^').startswith(prefix)
319 rst = helplist(select)
320 rst = helplist(select)
320 return rst
321 return rst
321
322
322 rst = []
323 rst = []
323
324
324 # check if it's an invalid alias and display its error if it is
325 # check if it's an invalid alias and display its error if it is
325 if getattr(entry[0], 'badalias', None):
326 if getattr(entry[0], 'badalias', None):
326 rst.append(entry[0].badalias + '\n')
327 rst.append(entry[0].badalias + '\n')
327 if entry[0].unknowncmd:
328 if entry[0].unknowncmd:
328 try:
329 try:
329 rst.extend(helpextcmd(entry[0].cmdname))
330 rst.extend(helpextcmd(entry[0].cmdname))
330 except error.UnknownCommand:
331 except error.UnknownCommand:
331 pass
332 pass
332 return rst
333 return rst
333
334
334 # synopsis
335 # synopsis
335 if len(entry) > 2:
336 if len(entry) > 2:
336 if entry[2].startswith('hg'):
337 if entry[2].startswith('hg'):
337 rst.append("%s\n" % entry[2])
338 rst.append("%s\n" % entry[2])
338 else:
339 else:
339 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
340 rst.append('hg %s %s\n' % (aliases[0], entry[2]))
340 else:
341 else:
341 rst.append('hg %s\n' % aliases[0])
342 rst.append('hg %s\n' % aliases[0])
342 # aliases
343 # aliases
343 if full and not ui.quiet and len(aliases) > 1:
344 if full and not ui.quiet and len(aliases) > 1:
344 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
345 rst.append(_("\naliases: %s\n") % ', '.join(aliases[1:]))
345 rst.append('\n')
346 rst.append('\n')
346
347
347 # description
348 # description
348 doc = gettext(entry[0].__doc__)
349 doc = gettext(pycompat.getdoc(entry[0]))
349 if not doc:
350 if not doc:
350 doc = _("(no help text available)")
351 doc = _("(no help text available)")
351 if util.safehasattr(entry[0], 'definition'): # aliased command
352 if util.safehasattr(entry[0], 'definition'): # aliased command
352 source = entry[0].source
353 source = entry[0].source
353 if entry[0].definition.startswith('!'): # shell alias
354 if entry[0].definition.startswith('!'): # shell alias
354 doc = (_('shell alias for::\n\n %s\n\ndefined by: %s\n') %
355 doc = (_('shell alias for::\n\n %s\n\ndefined by: %s\n') %
355 (entry[0].definition[1:], source))
356 (entry[0].definition[1:], source))
356 else:
357 else:
357 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
358 doc = (_('alias for: hg %s\n\n%s\n\ndefined by: %s\n') %
358 (entry[0].definition, doc, source))
359 (entry[0].definition, doc, source))
359 doc = doc.splitlines(True)
360 doc = doc.splitlines(True)
360 if ui.quiet or not full:
361 if ui.quiet or not full:
361 rst.append(doc[0])
362 rst.append(doc[0])
362 else:
363 else:
363 rst.extend(doc)
364 rst.extend(doc)
364 rst.append('\n')
365 rst.append('\n')
365
366
366 # check if this command shadows a non-trivial (multi-line)
367 # check if this command shadows a non-trivial (multi-line)
367 # extension help text
368 # extension help text
368 try:
369 try:
369 mod = extensions.find(name)
370 mod = extensions.find(name)
370 doc = gettext(mod.__doc__) or ''
371 doc = gettext(pycompat.getdoc(mod)) or ''
371 if '\n' in doc.strip():
372 if '\n' in doc.strip():
372 msg = _("(use 'hg help -e %s' to show help for "
373 msg = _("(use 'hg help -e %s' to show help for "
373 "the %s extension)") % (name, name)
374 "the %s extension)") % (name, name)
374 rst.append('\n%s\n' % msg)
375 rst.append('\n%s\n' % msg)
375 except KeyError:
376 except KeyError:
376 pass
377 pass
377
378
378 # options
379 # options
379 if not ui.quiet and entry[1]:
380 if not ui.quiet and entry[1]:
380 rst.append(optrst(_("options"), entry[1], ui.verbose))
381 rst.append(optrst(_("options"), entry[1], ui.verbose))
381
382
382 if ui.verbose:
383 if ui.verbose:
383 rst.append(optrst(_("global options"),
384 rst.append(optrst(_("global options"),
384 commands.globalopts, ui.verbose))
385 commands.globalopts, ui.verbose))
385
386
386 if not ui.verbose:
387 if not ui.verbose:
387 if not full:
388 if not full:
388 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
389 rst.append(_("\n(use 'hg %s -h' to show more help)\n")
389 % name)
390 % name)
390 elif not ui.quiet:
391 elif not ui.quiet:
391 rst.append(_('\n(some details hidden, use --verbose '
392 rst.append(_('\n(some details hidden, use --verbose '
392 'to show complete help)'))
393 'to show complete help)'))
393
394
394 return rst
395 return rst
395
396
396
397
397 def helplist(select=None, **opts):
398 def helplist(select=None, **opts):
398 # list of commands
399 # list of commands
399 if name == "shortlist":
400 if name == "shortlist":
400 header = _('basic commands:\n\n')
401 header = _('basic commands:\n\n')
401 elif name == "debug":
402 elif name == "debug":
402 header = _('debug commands (internal and unsupported):\n\n')
403 header = _('debug commands (internal and unsupported):\n\n')
403 else:
404 else:
404 header = _('list of commands:\n\n')
405 header = _('list of commands:\n\n')
405
406
406 h = {}
407 h = {}
407 cmds = {}
408 cmds = {}
408 for c, e in commands.table.iteritems():
409 for c, e in commands.table.iteritems():
409 f = c.partition("|")[0]
410 f = c.partition("|")[0]
410 if select and not select(f):
411 if select and not select(f):
411 continue
412 continue
412 if (not select and name != 'shortlist' and
413 if (not select and name != 'shortlist' and
413 e[0].__module__ != commands.__name__):
414 e[0].__module__ != commands.__name__):
414 continue
415 continue
415 if name == "shortlist" and not f.startswith("^"):
416 if name == "shortlist" and not f.startswith("^"):
416 continue
417 continue
417 f = f.lstrip("^")
418 f = f.lstrip("^")
418 doc = e[0].__doc__
419 doc = pycompat.getdoc(e[0])
419 if filtercmd(ui, f, name, doc):
420 if filtercmd(ui, f, name, doc):
420 continue
421 continue
421 doc = gettext(doc)
422 doc = gettext(doc)
422 if not doc:
423 if not doc:
423 doc = _("(no help text available)")
424 doc = _("(no help text available)")
424 h[f] = doc.splitlines()[0].rstrip()
425 h[f] = doc.splitlines()[0].rstrip()
425 cmds[f] = c.lstrip("^")
426 cmds[f] = c.lstrip("^")
426
427
427 rst = []
428 rst = []
428 if not h:
429 if not h:
429 if not ui.quiet:
430 if not ui.quiet:
430 rst.append(_('no commands defined\n'))
431 rst.append(_('no commands defined\n'))
431 return rst
432 return rst
432
433
433 if not ui.quiet:
434 if not ui.quiet:
434 rst.append(header)
435 rst.append(header)
435 fns = sorted(h)
436 fns = sorted(h)
436 for f in fns:
437 for f in fns:
437 if ui.verbose:
438 if ui.verbose:
438 commacmds = cmds[f].replace("|",", ")
439 commacmds = cmds[f].replace("|",", ")
439 rst.append(" :%s: %s\n" % (commacmds, h[f]))
440 rst.append(" :%s: %s\n" % (commacmds, h[f]))
440 else:
441 else:
441 rst.append(' :%s: %s\n' % (f, h[f]))
442 rst.append(' :%s: %s\n' % (f, h[f]))
442
443
443 ex = opts.get
444 ex = opts.get
444 anyopts = (ex('keyword') or not (ex('command') or ex('extension')))
445 anyopts = (ex('keyword') or not (ex('command') or ex('extension')))
445 if not name and anyopts:
446 if not name and anyopts:
446 exts = listexts(_('enabled extensions:'), extensions.enabled())
447 exts = listexts(_('enabled extensions:'), extensions.enabled())
447 if exts:
448 if exts:
448 rst.append('\n')
449 rst.append('\n')
449 rst.extend(exts)
450 rst.extend(exts)
450
451
451 rst.append(_("\nadditional help topics:\n\n"))
452 rst.append(_("\nadditional help topics:\n\n"))
452 topics = []
453 topics = []
453 for names, header, doc in helptable:
454 for names, header, doc in helptable:
454 topics.append((names[0], header))
455 topics.append((names[0], header))
455 for t, desc in topics:
456 for t, desc in topics:
456 rst.append(" :%s: %s\n" % (t, desc))
457 rst.append(" :%s: %s\n" % (t, desc))
457
458
458 if ui.quiet:
459 if ui.quiet:
459 pass
460 pass
460 elif ui.verbose:
461 elif ui.verbose:
461 rst.append('\n%s\n' % optrst(_("global options"),
462 rst.append('\n%s\n' % optrst(_("global options"),
462 commands.globalopts, ui.verbose))
463 commands.globalopts, ui.verbose))
463 if name == 'shortlist':
464 if name == 'shortlist':
464 rst.append(_("\n(use 'hg help' for the full list "
465 rst.append(_("\n(use 'hg help' for the full list "
465 "of commands)\n"))
466 "of commands)\n"))
466 else:
467 else:
467 if name == 'shortlist':
468 if name == 'shortlist':
468 rst.append(_("\n(use 'hg help' for the full list of commands "
469 rst.append(_("\n(use 'hg help' for the full list of commands "
469 "or 'hg -v' for details)\n"))
470 "or 'hg -v' for details)\n"))
470 elif name and not full:
471 elif name and not full:
471 rst.append(_("\n(use 'hg help %s' to show the full help "
472 rst.append(_("\n(use 'hg help %s' to show the full help "
472 "text)\n") % name)
473 "text)\n") % name)
473 elif name and cmds and name in cmds.keys():
474 elif name and cmds and name in cmds.keys():
474 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
475 rst.append(_("\n(use 'hg help -v -e %s' to show built-in "
475 "aliases and global options)\n") % name)
476 "aliases and global options)\n") % name)
476 else:
477 else:
477 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
478 rst.append(_("\n(use 'hg help -v%s' to show built-in aliases "
478 "and global options)\n")
479 "and global options)\n")
479 % (name and " " + name or ""))
480 % (name and " " + name or ""))
480 return rst
481 return rst
481
482
482 def helptopic(name, subtopic=None):
483 def helptopic(name, subtopic=None):
483 # Look for sub-topic entry first.
484 # Look for sub-topic entry first.
484 header, doc = None, None
485 header, doc = None, None
485 if subtopic and name in subtopics:
486 if subtopic and name in subtopics:
486 for names, header, doc in subtopics[name]:
487 for names, header, doc in subtopics[name]:
487 if subtopic in names:
488 if subtopic in names:
488 break
489 break
489
490
490 if not header:
491 if not header:
491 for names, header, doc in helptable:
492 for names, header, doc in helptable:
492 if name in names:
493 if name in names:
493 break
494 break
494 else:
495 else:
495 raise error.UnknownCommand(name)
496 raise error.UnknownCommand(name)
496
497
497 rst = [minirst.section(header)]
498 rst = [minirst.section(header)]
498
499
499 # description
500 # description
500 if not doc:
501 if not doc:
501 rst.append(" %s\n" % _("(no help text available)"))
502 rst.append(" %s\n" % _("(no help text available)"))
502 if callable(doc):
503 if callable(doc):
503 rst += [" %s\n" % l for l in doc(ui).splitlines()]
504 rst += [" %s\n" % l for l in doc(ui).splitlines()]
504
505
505 if not ui.verbose:
506 if not ui.verbose:
506 omitted = _('(some details hidden, use --verbose'
507 omitted = _('(some details hidden, use --verbose'
507 ' to show complete help)')
508 ' to show complete help)')
508 indicateomitted(rst, omitted)
509 indicateomitted(rst, omitted)
509
510
510 try:
511 try:
511 cmdutil.findcmd(name, commands.table)
512 cmdutil.findcmd(name, commands.table)
512 rst.append(_("\nuse 'hg help -c %s' to see help for "
513 rst.append(_("\nuse 'hg help -c %s' to see help for "
513 "the %s command\n") % (name, name))
514 "the %s command\n") % (name, name))
514 except error.UnknownCommand:
515 except error.UnknownCommand:
515 pass
516 pass
516 return rst
517 return rst
517
518
518 def helpext(name, subtopic=None):
519 def helpext(name, subtopic=None):
519 try:
520 try:
520 mod = extensions.find(name)
521 mod = extensions.find(name)
521 doc = gettext(mod.__doc__) or _('no help text available')
522 doc = gettext(pycompat.getdoc(mod)) or _('no help text available')
522 except KeyError:
523 except KeyError:
523 mod = None
524 mod = None
524 doc = extensions.disabledext(name)
525 doc = extensions.disabledext(name)
525 if not doc:
526 if not doc:
526 raise error.UnknownCommand(name)
527 raise error.UnknownCommand(name)
527
528
528 if '\n' not in doc:
529 if '\n' not in doc:
529 head, tail = doc, ""
530 head, tail = doc, ""
530 else:
531 else:
531 head, tail = doc.split('\n', 1)
532 head, tail = doc.split('\n', 1)
532 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
533 rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
533 if tail:
534 if tail:
534 rst.extend(tail.splitlines(True))
535 rst.extend(tail.splitlines(True))
535 rst.append('\n')
536 rst.append('\n')
536
537
537 if not ui.verbose:
538 if not ui.verbose:
538 omitted = _('(some details hidden, use --verbose'
539 omitted = _('(some details hidden, use --verbose'
539 ' to show complete help)')
540 ' to show complete help)')
540 indicateomitted(rst, omitted)
541 indicateomitted(rst, omitted)
541
542
542 if mod:
543 if mod:
543 try:
544 try:
544 ct = mod.cmdtable
545 ct = mod.cmdtable
545 except AttributeError:
546 except AttributeError:
546 ct = {}
547 ct = {}
547 modcmds = set([c.partition('|')[0] for c in ct])
548 modcmds = set([c.partition('|')[0] for c in ct])
548 rst.extend(helplist(modcmds.__contains__))
549 rst.extend(helplist(modcmds.__contains__))
549 else:
550 else:
550 rst.append(_("(use 'hg help extensions' for information on enabling"
551 rst.append(_("(use 'hg help extensions' for information on enabling"
551 " extensions)\n"))
552 " extensions)\n"))
552 return rst
553 return rst
553
554
554 def helpextcmd(name, subtopic=None):
555 def helpextcmd(name, subtopic=None):
555 cmd, ext, mod = extensions.disabledcmd(ui, name,
556 cmd, ext, mod = extensions.disabledcmd(ui, name,
556 ui.configbool('ui', 'strict'))
557 ui.configbool('ui', 'strict'))
557 doc = gettext(mod.__doc__).splitlines()[0]
558 doc = gettext(pycompat.getdoc(mod)).splitlines()[0]
558
559
559 rst = listexts(_("'%s' is provided by the following "
560 rst = listexts(_("'%s' is provided by the following "
560 "extension:") % cmd, {ext: doc}, indent=4,
561 "extension:") % cmd, {ext: doc}, indent=4,
561 showdeprecated=True)
562 showdeprecated=True)
562 rst.append('\n')
563 rst.append('\n')
563 rst.append(_("(use 'hg help extensions' for information on enabling "
564 rst.append(_("(use 'hg help extensions' for information on enabling "
564 "extensions)\n"))
565 "extensions)\n"))
565 return rst
566 return rst
566
567
567
568
568 rst = []
569 rst = []
569 kw = opts.get('keyword')
570 kw = opts.get('keyword')
570 if kw or name is None and any(opts[o] for o in opts):
571 if kw or name is None and any(opts[o] for o in opts):
571 matches = topicmatch(ui, commands, name or '')
572 matches = topicmatch(ui, commands, name or '')
572 helpareas = []
573 helpareas = []
573 if opts.get('extension'):
574 if opts.get('extension'):
574 helpareas += [('extensions', _('Extensions'))]
575 helpareas += [('extensions', _('Extensions'))]
575 if opts.get('command'):
576 if opts.get('command'):
576 helpareas += [('commands', _('Commands'))]
577 helpareas += [('commands', _('Commands'))]
577 if not helpareas:
578 if not helpareas:
578 helpareas = [('topics', _('Topics')),
579 helpareas = [('topics', _('Topics')),
579 ('commands', _('Commands')),
580 ('commands', _('Commands')),
580 ('extensions', _('Extensions')),
581 ('extensions', _('Extensions')),
581 ('extensioncommands', _('Extension Commands'))]
582 ('extensioncommands', _('Extension Commands'))]
582 for t, title in helpareas:
583 for t, title in helpareas:
583 if matches[t]:
584 if matches[t]:
584 rst.append('%s:\n\n' % title)
585 rst.append('%s:\n\n' % title)
585 rst.extend(minirst.maketable(sorted(matches[t]), 1))
586 rst.extend(minirst.maketable(sorted(matches[t]), 1))
586 rst.append('\n')
587 rst.append('\n')
587 if not rst:
588 if not rst:
588 msg = _('no matches')
589 msg = _('no matches')
589 hint = _("try 'hg help' for a list of topics")
590 hint = _("try 'hg help' for a list of topics")
590 raise error.Abort(msg, hint=hint)
591 raise error.Abort(msg, hint=hint)
591 elif name and name != 'shortlist':
592 elif name and name != 'shortlist':
592 queries = []
593 queries = []
593 if unknowncmd:
594 if unknowncmd:
594 queries += [helpextcmd]
595 queries += [helpextcmd]
595 if opts.get('extension'):
596 if opts.get('extension'):
596 queries += [helpext]
597 queries += [helpext]
597 if opts.get('command'):
598 if opts.get('command'):
598 queries += [helpcmd]
599 queries += [helpcmd]
599 if not queries:
600 if not queries:
600 queries = (helptopic, helpcmd, helpext, helpextcmd)
601 queries = (helptopic, helpcmd, helpext, helpextcmd)
601 for f in queries:
602 for f in queries:
602 try:
603 try:
603 rst = f(name, subtopic)
604 rst = f(name, subtopic)
604 break
605 break
605 except error.UnknownCommand:
606 except error.UnknownCommand:
606 pass
607 pass
607 else:
608 else:
608 if unknowncmd:
609 if unknowncmd:
609 raise error.UnknownCommand(name)
610 raise error.UnknownCommand(name)
610 else:
611 else:
611 msg = _('no such help topic: %s') % name
612 msg = _('no such help topic: %s') % name
612 hint = _("try 'hg help --keyword %s'") % name
613 hint = _("try 'hg help --keyword %s'") % name
613 raise error.Abort(msg, hint=hint)
614 raise error.Abort(msg, hint=hint)
614 else:
615 else:
615 # program name
616 # program name
616 if not ui.quiet:
617 if not ui.quiet:
617 rst = [_("Mercurial Distributed SCM\n"), '\n']
618 rst = [_("Mercurial Distributed SCM\n"), '\n']
618 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
619 rst.extend(helplist(None, **pycompat.strkwargs(opts)))
619
620
620 return ''.join(rst)
621 return ''.join(rst)
621
622
622 def formattedhelp(ui, commands, name, keep=None, unknowncmd=False, full=True,
623 def formattedhelp(ui, commands, name, keep=None, unknowncmd=False, full=True,
623 **opts):
624 **opts):
624 """get help for a given topic (as a dotted name) as rendered rst
625 """get help for a given topic (as a dotted name) as rendered rst
625
626
626 Either returns the rendered help text or raises an exception.
627 Either returns the rendered help text or raises an exception.
627 """
628 """
628 if keep is None:
629 if keep is None:
629 keep = []
630 keep = []
630 else:
631 else:
631 keep = list(keep) # make a copy so we can mutate this later
632 keep = list(keep) # make a copy so we can mutate this later
632 fullname = name
633 fullname = name
633 section = None
634 section = None
634 subtopic = None
635 subtopic = None
635 if name and '.' in name:
636 if name and '.' in name:
636 name, remaining = name.split('.', 1)
637 name, remaining = name.split('.', 1)
637 remaining = encoding.lower(remaining)
638 remaining = encoding.lower(remaining)
638 if '.' in remaining:
639 if '.' in remaining:
639 subtopic, section = remaining.split('.', 1)
640 subtopic, section = remaining.split('.', 1)
640 else:
641 else:
641 if name in subtopics:
642 if name in subtopics:
642 subtopic = remaining
643 subtopic = remaining
643 else:
644 else:
644 section = remaining
645 section = remaining
645 textwidth = ui.configint('ui', 'textwidth', 78)
646 textwidth = ui.configint('ui', 'textwidth', 78)
646 termwidth = ui.termwidth() - 2
647 termwidth = ui.termwidth() - 2
647 if textwidth <= 0 or termwidth < textwidth:
648 if textwidth <= 0 or termwidth < textwidth:
648 textwidth = termwidth
649 textwidth = termwidth
649 text = help_(ui, commands, name,
650 text = help_(ui, commands, name,
650 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
651 subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
651
652
652 formatted, pruned = minirst.format(text, textwidth, keep=keep,
653 formatted, pruned = minirst.format(text, textwidth, keep=keep,
653 section=section)
654 section=section)
654
655
655 # We could have been given a weird ".foo" section without a name
656 # We could have been given a weird ".foo" section without a name
656 # to look for, or we could have simply failed to found "foo.bar"
657 # to look for, or we could have simply failed to found "foo.bar"
657 # because bar isn't a section of foo
658 # because bar isn't a section of foo
658 if section and not (formatted and name):
659 if section and not (formatted and name):
659 raise error.Abort(_("help section not found: %s") % fullname)
660 raise error.Abort(_("help section not found: %s") % fullname)
660
661
661 if 'verbose' in pruned:
662 if 'verbose' in pruned:
662 keep.append('omitted')
663 keep.append('omitted')
663 else:
664 else:
664 keep.append('notomitted')
665 keep.append('notomitted')
665 formatted, pruned = minirst.format(text, textwidth, keep=keep,
666 formatted, pruned = minirst.format(text, textwidth, keep=keep,
666 section=section)
667 section=section)
667 return formatted
668 return formatted
@@ -1,429 +1,440 b''
1 # pycompat.py - portability shim for python 3
1 # pycompat.py - portability shim for python 3
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 """Mercurial portability shim for python 3.
6 """Mercurial portability shim for python 3.
7
7
8 This contains aliases to hide python version-specific details from the core.
8 This contains aliases to hide python version-specific details from the core.
9 """
9 """
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import getopt
13 import getopt
14 import os
14 import os
15 import shlex
15 import shlex
16 import sys
16 import sys
17
17
18 ispy3 = (sys.version_info[0] >= 3)
18 ispy3 = (sys.version_info[0] >= 3)
19
19
20 if not ispy3:
20 if not ispy3:
21 import cookielib
21 import cookielib
22 import cPickle as pickle
22 import cPickle as pickle
23 import httplib
23 import httplib
24 import Queue as _queue
24 import Queue as _queue
25 import SocketServer as socketserver
25 import SocketServer as socketserver
26 import xmlrpclib
26 import xmlrpclib
27 else:
27 else:
28 import http.cookiejar as cookielib
28 import http.cookiejar as cookielib
29 import http.client as httplib
29 import http.client as httplib
30 import pickle
30 import pickle
31 import queue as _queue
31 import queue as _queue
32 import socketserver
32 import socketserver
33 import xmlrpc.client as xmlrpclib
33 import xmlrpc.client as xmlrpclib
34
34
35 def identity(a):
35 def identity(a):
36 return a
36 return a
37
37
38 if ispy3:
38 if ispy3:
39 import builtins
39 import builtins
40 import functools
40 import functools
41 import io
41 import io
42 import struct
42 import struct
43
43
44 fsencode = os.fsencode
44 fsencode = os.fsencode
45 fsdecode = os.fsdecode
45 fsdecode = os.fsdecode
46 # A bytes version of os.name.
46 # A bytes version of os.name.
47 oslinesep = os.linesep.encode('ascii')
47 oslinesep = os.linesep.encode('ascii')
48 osname = os.name.encode('ascii')
48 osname = os.name.encode('ascii')
49 ospathsep = os.pathsep.encode('ascii')
49 ospathsep = os.pathsep.encode('ascii')
50 ossep = os.sep.encode('ascii')
50 ossep = os.sep.encode('ascii')
51 osaltsep = os.altsep
51 osaltsep = os.altsep
52 if osaltsep:
52 if osaltsep:
53 osaltsep = osaltsep.encode('ascii')
53 osaltsep = osaltsep.encode('ascii')
54 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
54 # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
55 # returns bytes.
55 # returns bytes.
56 getcwd = os.getcwdb
56 getcwd = os.getcwdb
57 sysplatform = sys.platform.encode('ascii')
57 sysplatform = sys.platform.encode('ascii')
58 sysexecutable = sys.executable
58 sysexecutable = sys.executable
59 if sysexecutable:
59 if sysexecutable:
60 sysexecutable = os.fsencode(sysexecutable)
60 sysexecutable = os.fsencode(sysexecutable)
61 stringio = io.BytesIO
61 stringio = io.BytesIO
62 maplist = lambda *args: list(map(*args))
62 maplist = lambda *args: list(map(*args))
63
63
64 # TODO: .buffer might not exist if std streams were replaced; we'll need
64 # TODO: .buffer might not exist if std streams were replaced; we'll need
65 # a silly wrapper to make a bytes stream backed by a unicode one.
65 # a silly wrapper to make a bytes stream backed by a unicode one.
66 stdin = sys.stdin.buffer
66 stdin = sys.stdin.buffer
67 stdout = sys.stdout.buffer
67 stdout = sys.stdout.buffer
68 stderr = sys.stderr.buffer
68 stderr = sys.stderr.buffer
69
69
70 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
70 # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
71 # we can use os.fsencode() to get back bytes argv.
71 # we can use os.fsencode() to get back bytes argv.
72 #
72 #
73 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
73 # https://hg.python.org/cpython/file/v3.5.1/Programs/python.c#l55
74 #
74 #
75 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
75 # TODO: On Windows, the native argv is wchar_t, so we'll need a different
76 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
76 # workaround to simulate the Python 2 (i.e. ANSI Win32 API) behavior.
77 if getattr(sys, 'argv', None) is not None:
77 if getattr(sys, 'argv', None) is not None:
78 sysargv = list(map(os.fsencode, sys.argv))
78 sysargv = list(map(os.fsencode, sys.argv))
79
79
80 bytechr = struct.Struct('>B').pack
80 bytechr = struct.Struct('>B').pack
81
81
82 class bytestr(bytes):
82 class bytestr(bytes):
83 """A bytes which mostly acts as a Python 2 str
83 """A bytes which mostly acts as a Python 2 str
84
84
85 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
85 >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
86 (b'', b'foo', b'ascii', b'1')
86 (b'', b'foo', b'ascii', b'1')
87 >>> s = bytestr(b'foo')
87 >>> s = bytestr(b'foo')
88 >>> assert s is bytestr(s)
88 >>> assert s is bytestr(s)
89
89
90 __bytes__() should be called if provided:
90 __bytes__() should be called if provided:
91
91
92 >>> class bytesable(object):
92 >>> class bytesable(object):
93 ... def __bytes__(self):
93 ... def __bytes__(self):
94 ... return b'bytes'
94 ... return b'bytes'
95 >>> bytestr(bytesable())
95 >>> bytestr(bytesable())
96 b'bytes'
96 b'bytes'
97
97
98 There's no implicit conversion from non-ascii str as its encoding is
98 There's no implicit conversion from non-ascii str as its encoding is
99 unknown:
99 unknown:
100
100
101 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
101 >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
102 Traceback (most recent call last):
102 Traceback (most recent call last):
103 ...
103 ...
104 UnicodeEncodeError: ...
104 UnicodeEncodeError: ...
105
105
106 Comparison between bytestr and bytes should work:
106 Comparison between bytestr and bytes should work:
107
107
108 >>> assert bytestr(b'foo') == b'foo'
108 >>> assert bytestr(b'foo') == b'foo'
109 >>> assert b'foo' == bytestr(b'foo')
109 >>> assert b'foo' == bytestr(b'foo')
110 >>> assert b'f' in bytestr(b'foo')
110 >>> assert b'f' in bytestr(b'foo')
111 >>> assert bytestr(b'f') in b'foo'
111 >>> assert bytestr(b'f') in b'foo'
112
112
113 Sliced elements should be bytes, not integer:
113 Sliced elements should be bytes, not integer:
114
114
115 >>> s[1], s[:2]
115 >>> s[1], s[:2]
116 (b'o', b'fo')
116 (b'o', b'fo')
117 >>> list(s), list(reversed(s))
117 >>> list(s), list(reversed(s))
118 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
118 ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
119
119
120 As bytestr type isn't propagated across operations, you need to cast
120 As bytestr type isn't propagated across operations, you need to cast
121 bytes to bytestr explicitly:
121 bytes to bytestr explicitly:
122
122
123 >>> s = bytestr(b'foo').upper()
123 >>> s = bytestr(b'foo').upper()
124 >>> t = bytestr(s)
124 >>> t = bytestr(s)
125 >>> s[0], t[0]
125 >>> s[0], t[0]
126 (70, b'F')
126 (70, b'F')
127
127
128 Be careful to not pass a bytestr object to a function which expects
128 Be careful to not pass a bytestr object to a function which expects
129 bytearray-like behavior.
129 bytearray-like behavior.
130
130
131 >>> t = bytes(t) # cast to bytes
131 >>> t = bytes(t) # cast to bytes
132 >>> assert type(t) is bytes
132 >>> assert type(t) is bytes
133 """
133 """
134
134
135 def __new__(cls, s=b''):
135 def __new__(cls, s=b''):
136 if isinstance(s, bytestr):
136 if isinstance(s, bytestr):
137 return s
137 return s
138 if (not isinstance(s, (bytes, bytearray))
138 if (not isinstance(s, (bytes, bytearray))
139 and not hasattr(s, u'__bytes__')): # hasattr-py3-only
139 and not hasattr(s, u'__bytes__')): # hasattr-py3-only
140 s = str(s).encode(u'ascii')
140 s = str(s).encode(u'ascii')
141 return bytes.__new__(cls, s)
141 return bytes.__new__(cls, s)
142
142
143 def __getitem__(self, key):
143 def __getitem__(self, key):
144 s = bytes.__getitem__(self, key)
144 s = bytes.__getitem__(self, key)
145 if not isinstance(s, bytes):
145 if not isinstance(s, bytes):
146 s = bytechr(s)
146 s = bytechr(s)
147 return s
147 return s
148
148
149 def __iter__(self):
149 def __iter__(self):
150 return iterbytestr(bytes.__iter__(self))
150 return iterbytestr(bytes.__iter__(self))
151
151
152 def iterbytestr(s):
152 def iterbytestr(s):
153 """Iterate bytes as if it were a str object of Python 2"""
153 """Iterate bytes as if it were a str object of Python 2"""
154 return map(bytechr, s)
154 return map(bytechr, s)
155
155
156 def sysbytes(s):
156 def sysbytes(s):
157 """Convert an internal str (e.g. keyword, __doc__) back to bytes
157 """Convert an internal str (e.g. keyword, __doc__) back to bytes
158
158
159 This never raises UnicodeEncodeError, but only ASCII characters
159 This never raises UnicodeEncodeError, but only ASCII characters
160 can be round-trip by sysstr(sysbytes(s)).
160 can be round-trip by sysstr(sysbytes(s)).
161 """
161 """
162 return s.encode(u'utf-8')
162 return s.encode(u'utf-8')
163
163
164 def sysstr(s):
164 def sysstr(s):
165 """Return a keyword str to be passed to Python functions such as
165 """Return a keyword str to be passed to Python functions such as
166 getattr() and str.encode()
166 getattr() and str.encode()
167
167
168 This never raises UnicodeDecodeError. Non-ascii characters are
168 This never raises UnicodeDecodeError. Non-ascii characters are
169 considered invalid and mapped to arbitrary but unique code points
169 considered invalid and mapped to arbitrary but unique code points
170 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
170 such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
171 """
171 """
172 if isinstance(s, builtins.str):
172 if isinstance(s, builtins.str):
173 return s
173 return s
174 return s.decode(u'latin-1')
174 return s.decode(u'latin-1')
175
175
176 def raisewithtb(exc, tb):
176 def raisewithtb(exc, tb):
177 """Raise exception with the given traceback"""
177 """Raise exception with the given traceback"""
178 raise exc.with_traceback(tb)
178 raise exc.with_traceback(tb)
179
179
180 def getdoc(obj):
181 """Get docstring as bytes; may be None so gettext() won't confuse it
182 with _('')"""
183 doc = getattr(obj, u'__doc__', None)
184 if doc is None:
185 return doc
186 return sysbytes(doc)
187
180 def _wrapattrfunc(f):
188 def _wrapattrfunc(f):
181 @functools.wraps(f)
189 @functools.wraps(f)
182 def w(object, name, *args):
190 def w(object, name, *args):
183 return f(object, sysstr(name), *args)
191 return f(object, sysstr(name), *args)
184 return w
192 return w
185
193
186 # these wrappers are automagically imported by hgloader
194 # these wrappers are automagically imported by hgloader
187 delattr = _wrapattrfunc(builtins.delattr)
195 delattr = _wrapattrfunc(builtins.delattr)
188 getattr = _wrapattrfunc(builtins.getattr)
196 getattr = _wrapattrfunc(builtins.getattr)
189 hasattr = _wrapattrfunc(builtins.hasattr)
197 hasattr = _wrapattrfunc(builtins.hasattr)
190 setattr = _wrapattrfunc(builtins.setattr)
198 setattr = _wrapattrfunc(builtins.setattr)
191 xrange = builtins.range
199 xrange = builtins.range
192 unicode = str
200 unicode = str
193
201
194 def open(name, mode='r', buffering=-1):
202 def open(name, mode='r', buffering=-1):
195 return builtins.open(name, sysstr(mode), buffering)
203 return builtins.open(name, sysstr(mode), buffering)
196
204
197 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
205 # getopt.getopt() on Python 3 deals with unicodes internally so we cannot
198 # pass bytes there. Passing unicodes will result in unicodes as return
206 # pass bytes there. Passing unicodes will result in unicodes as return
199 # values which we need to convert again to bytes.
207 # values which we need to convert again to bytes.
200 def getoptb(args, shortlist, namelist):
208 def getoptb(args, shortlist, namelist):
201 args = [a.decode('latin-1') for a in args]
209 args = [a.decode('latin-1') for a in args]
202 shortlist = shortlist.decode('latin-1')
210 shortlist = shortlist.decode('latin-1')
203 namelist = [a.decode('latin-1') for a in namelist]
211 namelist = [a.decode('latin-1') for a in namelist]
204 opts, args = getopt.getopt(args, shortlist, namelist)
212 opts, args = getopt.getopt(args, shortlist, namelist)
205 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
213 opts = [(a[0].encode('latin-1'), a[1].encode('latin-1'))
206 for a in opts]
214 for a in opts]
207 args = [a.encode('latin-1') for a in args]
215 args = [a.encode('latin-1') for a in args]
208 return opts, args
216 return opts, args
209
217
210 # keys of keyword arguments in Python need to be strings which are unicodes
218 # keys of keyword arguments in Python need to be strings which are unicodes
211 # Python 3. This function takes keyword arguments, convert the keys to str.
219 # Python 3. This function takes keyword arguments, convert the keys to str.
212 def strkwargs(dic):
220 def strkwargs(dic):
213 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
221 dic = dict((k.decode('latin-1'), v) for k, v in dic.iteritems())
214 return dic
222 return dic
215
223
216 # keys of keyword arguments need to be unicode while passing into
224 # keys of keyword arguments need to be unicode while passing into
217 # a function. This function helps us to convert those keys back to bytes
225 # a function. This function helps us to convert those keys back to bytes
218 # again as we need to deal with bytes.
226 # again as we need to deal with bytes.
219 def byteskwargs(dic):
227 def byteskwargs(dic):
220 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
228 dic = dict((k.encode('latin-1'), v) for k, v in dic.iteritems())
221 return dic
229 return dic
222
230
223 # shlex.split() accepts unicodes on Python 3. This function takes bytes
231 # shlex.split() accepts unicodes on Python 3. This function takes bytes
224 # argument, convert it into unicodes, pass into shlex.split(), convert the
232 # argument, convert it into unicodes, pass into shlex.split(), convert the
225 # returned value to bytes and return that.
233 # returned value to bytes and return that.
226 # TODO: handle shlex.shlex().
234 # TODO: handle shlex.shlex().
227 def shlexsplit(s):
235 def shlexsplit(s):
228 ret = shlex.split(s.decode('latin-1'))
236 ret = shlex.split(s.decode('latin-1'))
229 return [a.encode('latin-1') for a in ret]
237 return [a.encode('latin-1') for a in ret]
230
238
231 else:
239 else:
232 import cStringIO
240 import cStringIO
233
241
234 bytechr = chr
242 bytechr = chr
235 bytestr = str
243 bytestr = str
236 iterbytestr = iter
244 iterbytestr = iter
237 sysbytes = identity
245 sysbytes = identity
238 sysstr = identity
246 sysstr = identity
239
247
240 # this can't be parsed on Python 3
248 # this can't be parsed on Python 3
241 exec('def raisewithtb(exc, tb):\n'
249 exec('def raisewithtb(exc, tb):\n'
242 ' raise exc, None, tb\n')
250 ' raise exc, None, tb\n')
243
251
244 # Partial backport from os.py in Python 3, which only accepts bytes.
252 # Partial backport from os.py in Python 3, which only accepts bytes.
245 # In Python 2, our paths should only ever be bytes, a unicode path
253 # In Python 2, our paths should only ever be bytes, a unicode path
246 # indicates a bug.
254 # indicates a bug.
247 def fsencode(filename):
255 def fsencode(filename):
248 if isinstance(filename, str):
256 if isinstance(filename, str):
249 return filename
257 return filename
250 else:
258 else:
251 raise TypeError(
259 raise TypeError(
252 "expect str, not %s" % type(filename).__name__)
260 "expect str, not %s" % type(filename).__name__)
253
261
254 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
262 # In Python 2, fsdecode() has a very chance to receive bytes. So it's
255 # better not to touch Python 2 part as it's already working fine.
263 # better not to touch Python 2 part as it's already working fine.
256 fsdecode = identity
264 fsdecode = identity
257
265
266 def getdoc(obj):
267 return getattr(obj, '__doc__', None)
268
258 def getoptb(args, shortlist, namelist):
269 def getoptb(args, shortlist, namelist):
259 return getopt.getopt(args, shortlist, namelist)
270 return getopt.getopt(args, shortlist, namelist)
260
271
261 strkwargs = identity
272 strkwargs = identity
262 byteskwargs = identity
273 byteskwargs = identity
263
274
264 oslinesep = os.linesep
275 oslinesep = os.linesep
265 osname = os.name
276 osname = os.name
266 ospathsep = os.pathsep
277 ospathsep = os.pathsep
267 ossep = os.sep
278 ossep = os.sep
268 osaltsep = os.altsep
279 osaltsep = os.altsep
269 stdin = sys.stdin
280 stdin = sys.stdin
270 stdout = sys.stdout
281 stdout = sys.stdout
271 stderr = sys.stderr
282 stderr = sys.stderr
272 if getattr(sys, 'argv', None) is not None:
283 if getattr(sys, 'argv', None) is not None:
273 sysargv = sys.argv
284 sysargv = sys.argv
274 sysplatform = sys.platform
285 sysplatform = sys.platform
275 getcwd = os.getcwd
286 getcwd = os.getcwd
276 sysexecutable = sys.executable
287 sysexecutable = sys.executable
277 shlexsplit = shlex.split
288 shlexsplit = shlex.split
278 stringio = cStringIO.StringIO
289 stringio = cStringIO.StringIO
279 maplist = map
290 maplist = map
280
291
281 empty = _queue.Empty
292 empty = _queue.Empty
282 queue = _queue.Queue
293 queue = _queue.Queue
283
294
284 class _pycompatstub(object):
295 class _pycompatstub(object):
285 def __init__(self):
296 def __init__(self):
286 self._aliases = {}
297 self._aliases = {}
287
298
288 def _registeraliases(self, origin, items):
299 def _registeraliases(self, origin, items):
289 """Add items that will be populated at the first access"""
300 """Add items that will be populated at the first access"""
290 items = map(sysstr, items)
301 items = map(sysstr, items)
291 self._aliases.update(
302 self._aliases.update(
292 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
303 (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
293 for item in items)
304 for item in items)
294
305
295 def _registeralias(self, origin, attr, name):
306 def _registeralias(self, origin, attr, name):
296 """Alias ``origin``.``attr`` as ``name``"""
307 """Alias ``origin``.``attr`` as ``name``"""
297 self._aliases[sysstr(name)] = (origin, sysstr(attr))
308 self._aliases[sysstr(name)] = (origin, sysstr(attr))
298
309
299 def __getattr__(self, name):
310 def __getattr__(self, name):
300 try:
311 try:
301 origin, item = self._aliases[name]
312 origin, item = self._aliases[name]
302 except KeyError:
313 except KeyError:
303 raise AttributeError(name)
314 raise AttributeError(name)
304 self.__dict__[name] = obj = getattr(origin, item)
315 self.__dict__[name] = obj = getattr(origin, item)
305 return obj
316 return obj
306
317
307 httpserver = _pycompatstub()
318 httpserver = _pycompatstub()
308 urlreq = _pycompatstub()
319 urlreq = _pycompatstub()
309 urlerr = _pycompatstub()
320 urlerr = _pycompatstub()
310 if not ispy3:
321 if not ispy3:
311 import BaseHTTPServer
322 import BaseHTTPServer
312 import CGIHTTPServer
323 import CGIHTTPServer
313 import SimpleHTTPServer
324 import SimpleHTTPServer
314 import urllib2
325 import urllib2
315 import urllib
326 import urllib
316 import urlparse
327 import urlparse
317 urlreq._registeraliases(urllib, (
328 urlreq._registeraliases(urllib, (
318 "addclosehook",
329 "addclosehook",
319 "addinfourl",
330 "addinfourl",
320 "ftpwrapper",
331 "ftpwrapper",
321 "pathname2url",
332 "pathname2url",
322 "quote",
333 "quote",
323 "splitattr",
334 "splitattr",
324 "splitpasswd",
335 "splitpasswd",
325 "splitport",
336 "splitport",
326 "splituser",
337 "splituser",
327 "unquote",
338 "unquote",
328 "url2pathname",
339 "url2pathname",
329 "urlencode",
340 "urlencode",
330 ))
341 ))
331 urlreq._registeraliases(urllib2, (
342 urlreq._registeraliases(urllib2, (
332 "AbstractHTTPHandler",
343 "AbstractHTTPHandler",
333 "BaseHandler",
344 "BaseHandler",
334 "build_opener",
345 "build_opener",
335 "FileHandler",
346 "FileHandler",
336 "FTPHandler",
347 "FTPHandler",
337 "HTTPBasicAuthHandler",
348 "HTTPBasicAuthHandler",
338 "HTTPDigestAuthHandler",
349 "HTTPDigestAuthHandler",
339 "HTTPHandler",
350 "HTTPHandler",
340 "HTTPPasswordMgrWithDefaultRealm",
351 "HTTPPasswordMgrWithDefaultRealm",
341 "HTTPSHandler",
352 "HTTPSHandler",
342 "install_opener",
353 "install_opener",
343 "ProxyHandler",
354 "ProxyHandler",
344 "Request",
355 "Request",
345 "urlopen",
356 "urlopen",
346 ))
357 ))
347 urlreq._registeraliases(urlparse, (
358 urlreq._registeraliases(urlparse, (
348 "urlparse",
359 "urlparse",
349 "urlunparse",
360 "urlunparse",
350 ))
361 ))
351 urlerr._registeraliases(urllib2, (
362 urlerr._registeraliases(urllib2, (
352 "HTTPError",
363 "HTTPError",
353 "URLError",
364 "URLError",
354 ))
365 ))
355 httpserver._registeraliases(BaseHTTPServer, (
366 httpserver._registeraliases(BaseHTTPServer, (
356 "HTTPServer",
367 "HTTPServer",
357 "BaseHTTPRequestHandler",
368 "BaseHTTPRequestHandler",
358 ))
369 ))
359 httpserver._registeraliases(SimpleHTTPServer, (
370 httpserver._registeraliases(SimpleHTTPServer, (
360 "SimpleHTTPRequestHandler",
371 "SimpleHTTPRequestHandler",
361 ))
372 ))
362 httpserver._registeraliases(CGIHTTPServer, (
373 httpserver._registeraliases(CGIHTTPServer, (
363 "CGIHTTPRequestHandler",
374 "CGIHTTPRequestHandler",
364 ))
375 ))
365
376
366 else:
377 else:
367 import urllib.parse
378 import urllib.parse
368 urlreq._registeraliases(urllib.parse, (
379 urlreq._registeraliases(urllib.parse, (
369 "splitattr",
380 "splitattr",
370 "splitpasswd",
381 "splitpasswd",
371 "splitport",
382 "splitport",
372 "splituser",
383 "splituser",
373 "urlparse",
384 "urlparse",
374 "urlunparse",
385 "urlunparse",
375 ))
386 ))
376 urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
387 urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
377 import urllib.request
388 import urllib.request
378 urlreq._registeraliases(urllib.request, (
389 urlreq._registeraliases(urllib.request, (
379 "AbstractHTTPHandler",
390 "AbstractHTTPHandler",
380 "BaseHandler",
391 "BaseHandler",
381 "build_opener",
392 "build_opener",
382 "FileHandler",
393 "FileHandler",
383 "FTPHandler",
394 "FTPHandler",
384 "ftpwrapper",
395 "ftpwrapper",
385 "HTTPHandler",
396 "HTTPHandler",
386 "HTTPSHandler",
397 "HTTPSHandler",
387 "install_opener",
398 "install_opener",
388 "pathname2url",
399 "pathname2url",
389 "HTTPBasicAuthHandler",
400 "HTTPBasicAuthHandler",
390 "HTTPDigestAuthHandler",
401 "HTTPDigestAuthHandler",
391 "HTTPPasswordMgrWithDefaultRealm",
402 "HTTPPasswordMgrWithDefaultRealm",
392 "ProxyHandler",
403 "ProxyHandler",
393 "Request",
404 "Request",
394 "url2pathname",
405 "url2pathname",
395 "urlopen",
406 "urlopen",
396 ))
407 ))
397 import urllib.response
408 import urllib.response
398 urlreq._registeraliases(urllib.response, (
409 urlreq._registeraliases(urllib.response, (
399 "addclosehook",
410 "addclosehook",
400 "addinfourl",
411 "addinfourl",
401 ))
412 ))
402 import urllib.error
413 import urllib.error
403 urlerr._registeraliases(urllib.error, (
414 urlerr._registeraliases(urllib.error, (
404 "HTTPError",
415 "HTTPError",
405 "URLError",
416 "URLError",
406 ))
417 ))
407 import http.server
418 import http.server
408 httpserver._registeraliases(http.server, (
419 httpserver._registeraliases(http.server, (
409 "HTTPServer",
420 "HTTPServer",
410 "BaseHTTPRequestHandler",
421 "BaseHTTPRequestHandler",
411 "SimpleHTTPRequestHandler",
422 "SimpleHTTPRequestHandler",
412 "CGIHTTPRequestHandler",
423 "CGIHTTPRequestHandler",
413 ))
424 ))
414
425
415 # urllib.parse.quote() accepts both str and bytes, decodes bytes
426 # urllib.parse.quote() accepts both str and bytes, decodes bytes
416 # (if necessary), and returns str. This is wonky. We provide a custom
427 # (if necessary), and returns str. This is wonky. We provide a custom
417 # implementation that only accepts bytes and emits bytes.
428 # implementation that only accepts bytes and emits bytes.
418 def quote(s, safe=r'/'):
429 def quote(s, safe=r'/'):
419 s = urllib.parse.quote_from_bytes(s, safe=safe)
430 s = urllib.parse.quote_from_bytes(s, safe=safe)
420 return s.encode('ascii', 'strict')
431 return s.encode('ascii', 'strict')
421
432
422 # urllib.parse.urlencode() returns str. We use this function to make
433 # urllib.parse.urlencode() returns str. We use this function to make
423 # sure we return bytes.
434 # sure we return bytes.
424 def urlencode(query, doseq=False):
435 def urlencode(query, doseq=False):
425 s = urllib.parse.urlencode(query, doseq=doseq)
436 s = urllib.parse.urlencode(query, doseq=doseq)
426 return s.encode('ascii')
437 return s.encode('ascii')
427
438
428 urlreq.quote = quote
439 urlreq.quote = quote
429 urlreq.urlencode = urlencode
440 urlreq.urlencode = urlencode
@@ -1,198 +1,212 b''
1 #require py3exe
1 #require py3exe
2
2
3 This test helps in keeping a track on which commands we can run on
3 This test helps in keeping a track on which commands we can run on
4 Python 3 and see what kind of errors are coming up.
4 Python 3 and see what kind of errors are coming up.
5 The full traceback is hidden to have a stable output.
5 The full traceback is hidden to have a stable output.
6 $ HGBIN=`which hg`
6 $ HGBIN=`which hg`
7
7
8 $ for cmd in version debuginstall ; do
8 $ for cmd in version debuginstall ; do
9 > echo $cmd
9 > echo $cmd
10 > $PYTHON3 $HGBIN $cmd 2>&1 2>&1 | tail -1
10 > $PYTHON3 $HGBIN $cmd 2>&1 2>&1 | tail -1
11 > done
11 > done
12 version
12 version
13 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 debuginstall
14 debuginstall
15 no problems detected
15 no problems detected
16
16
17 #if test-repo
17 #if test-repo
18 Make a clone so that any features in the developer's .hg/hgrc that
18 Make a clone so that any features in the developer's .hg/hgrc that
19 might confuse Python 3 don't break this test. When we can do commit in
19 might confuse Python 3 don't break this test. When we can do commit in
20 Python 3, we'll stop doing this. We use e76ed1e480ef for the clone
20 Python 3, we'll stop doing this. We use e76ed1e480ef for the clone
21 because it has different files than 273ce12ad8f1, so we can test both
21 because it has different files than 273ce12ad8f1, so we can test both
22 `files` from dirstate and `files` loaded from a specific revision.
22 `files` from dirstate and `files` loaded from a specific revision.
23
23
24 $ hg clone -r e76ed1e480ef "`dirname "$TESTDIR"`" testrepo 2>&1 | tail -1
24 $ hg clone -r e76ed1e480ef "`dirname "$TESTDIR"`" testrepo 2>&1 | tail -1
25 15 files updated, 0 files merged, 0 files removed, 0 files unresolved
25 15 files updated, 0 files merged, 0 files removed, 0 files unresolved
26
26
27 Test using -R, which exercises some URL code:
27 Test using -R, which exercises some URL code:
28 $ $PYTHON3 $HGBIN -R testrepo files -r 273ce12ad8f1 | tail -1
28 $ $PYTHON3 $HGBIN -R testrepo files -r 273ce12ad8f1 | tail -1
29 testrepo/tkmerge
29 testrepo/tkmerge
30
30
31 Now prove `hg files` is reading the whole manifest. We have to grep
31 Now prove `hg files` is reading the whole manifest. We have to grep
32 out some potential warnings that come from hgrc as yet.
32 out some potential warnings that come from hgrc as yet.
33 $ cd testrepo
33 $ cd testrepo
34 $ $PYTHON3 $HGBIN files -r 273ce12ad8f1
34 $ $PYTHON3 $HGBIN files -r 273ce12ad8f1
35 .hgignore
35 .hgignore
36 PKG-INFO
36 PKG-INFO
37 README
37 README
38 hg
38 hg
39 mercurial/__init__.py
39 mercurial/__init__.py
40 mercurial/byterange.py
40 mercurial/byterange.py
41 mercurial/fancyopts.py
41 mercurial/fancyopts.py
42 mercurial/hg.py
42 mercurial/hg.py
43 mercurial/mdiff.py
43 mercurial/mdiff.py
44 mercurial/revlog.py
44 mercurial/revlog.py
45 mercurial/transaction.py
45 mercurial/transaction.py
46 notes.txt
46 notes.txt
47 setup.py
47 setup.py
48 tkmerge
48 tkmerge
49
49
50 $ $PYTHON3 $HGBIN files -r 273ce12ad8f1 | wc -l
50 $ $PYTHON3 $HGBIN files -r 273ce12ad8f1 | wc -l
51 \s*14 (re)
51 \s*14 (re)
52 $ $PYTHON3 $HGBIN files | wc -l
52 $ $PYTHON3 $HGBIN files | wc -l
53 \s*15 (re)
53 \s*15 (re)
54
54
55 Test if log-like commands work:
55 Test if log-like commands work:
56
56
57 $ $PYTHON3 $HGBIN tip
57 $ $PYTHON3 $HGBIN tip
58 changeset: 10:e76ed1e480ef
58 changeset: 10:e76ed1e480ef
59 tag: tip
59 tag: tip
60 user: oxymoron@cinder.waste.org
60 user: oxymoron@cinder.waste.org
61 date: Tue May 03 23:37:43 2005 -0800
61 date: Tue May 03 23:37:43 2005 -0800
62 summary: Fix linking of changeset revs when merging
62 summary: Fix linking of changeset revs when merging
63
63
64
64
65 $ $PYTHON3 $HGBIN log -r0
65 $ $PYTHON3 $HGBIN log -r0
66 changeset: 0:9117c6561b0b
66 changeset: 0:9117c6561b0b
67 user: mpm@selenic.com
67 user: mpm@selenic.com
68 date: Tue May 03 13:16:10 2005 -0800
68 date: Tue May 03 13:16:10 2005 -0800
69 summary: Add back links from file revisions to changeset revisions
69 summary: Add back links from file revisions to changeset revisions
70
70
71
71
72 $ cd ..
72 $ cd ..
73 #endif
73 #endif
74
74
75 Test if `hg config` works:
75 Test if `hg config` works:
76
76
77 $ $PYTHON3 $HGBIN config
77 $ $PYTHON3 $HGBIN config
78 devel.all-warnings=true
78 devel.all-warnings=true
79 devel.default-date=0 0
79 devel.default-date=0 0
80 largefiles.usercache=$TESTTMP/.cache/largefiles
80 largefiles.usercache=$TESTTMP/.cache/largefiles
81 ui.slash=True
81 ui.slash=True
82 ui.interactive=False
82 ui.interactive=False
83 ui.mergemarkers=detailed
83 ui.mergemarkers=detailed
84 ui.promptecho=True
84 ui.promptecho=True
85 web.address=localhost
85 web.address=localhost
86 web.ipv6=False
86 web.ipv6=False
87
87
88 $ cat > included-hgrc <<EOF
88 $ cat > included-hgrc <<EOF
89 > [extensions]
89 > [extensions]
90 > babar = imaginary_elephant
90 > babar = imaginary_elephant
91 > EOF
91 > EOF
92 $ cat >> $HGRCPATH <<EOF
92 $ cat >> $HGRCPATH <<EOF
93 > %include $TESTTMP/included-hgrc
93 > %include $TESTTMP/included-hgrc
94 > EOF
94 > EOF
95 $ $PYTHON3 $HGBIN version | tail -1
95 $ $PYTHON3 $HGBIN version | tail -1
96 *** failed to import extension babar from imaginary_elephant: *: 'imaginary_elephant' (glob)
96 *** failed to import extension babar from imaginary_elephant: *: 'imaginary_elephant' (glob)
97 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
97 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
98
98
99 $ rm included-hgrc
99 $ rm included-hgrc
100 $ touch included-hgrc
100 $ touch included-hgrc
101
101
102 Test bytes-ness of policy.policy with HGMODULEPOLICY
102 Test bytes-ness of policy.policy with HGMODULEPOLICY
103
103
104 $ HGMODULEPOLICY=py
104 $ HGMODULEPOLICY=py
105 $ export HGMODULEPOLICY
105 $ export HGMODULEPOLICY
106 $ $PYTHON3 `which hg` debuginstall 2>&1 2>&1 | tail -1
106 $ $PYTHON3 `which hg` debuginstall 2>&1 2>&1 | tail -1
107 no problems detected
107 no problems detected
108
108
109 `hg init` can create empty repos
109 `hg init` can create empty repos
110 `hg status works fine`
110 `hg status works fine`
111 `hg summary` also works!
111 `hg summary` also works!
112
112
113 $ $PYTHON3 `which hg` init py3repo
113 $ $PYTHON3 `which hg` init py3repo
114 $ cd py3repo
114 $ cd py3repo
115 $ echo "This is the file 'iota'." > iota
115 $ echo "This is the file 'iota'." > iota
116 $ $PYTHON3 $HGBIN status
116 $ $PYTHON3 $HGBIN status
117 ? iota
117 ? iota
118 $ $PYTHON3 $HGBIN add iota
118 $ $PYTHON3 $HGBIN add iota
119 $ $PYTHON3 $HGBIN status
119 $ $PYTHON3 $HGBIN status
120 A iota
120 A iota
121 $ $PYTHON3 $HGBIN commit --message 'commit performed in Python 3'
121 $ $PYTHON3 $HGBIN commit --message 'commit performed in Python 3'
122 $ $PYTHON3 $HGBIN status
122 $ $PYTHON3 $HGBIN status
123
123
124 $ mkdir A
124 $ mkdir A
125 $ echo "This is the file 'mu'." > A/mu
125 $ echo "This is the file 'mu'." > A/mu
126 $ $PYTHON3 $HGBIN addremove
126 $ $PYTHON3 $HGBIN addremove
127 adding A/mu
127 adding A/mu
128 $ $PYTHON3 $HGBIN status
128 $ $PYTHON3 $HGBIN status
129 A A/mu
129 A A/mu
130 $ HGEDITOR='echo message > ' $PYTHON3 $HGBIN commit
130 $ HGEDITOR='echo message > ' $PYTHON3 $HGBIN commit
131 $ $PYTHON3 $HGBIN status
131 $ $PYTHON3 $HGBIN status
132 $ $PYHON3 $HGBIN summary
132 $ $PYHON3 $HGBIN summary
133 parent: 1:e1e9167203d4 tip
133 parent: 1:e1e9167203d4 tip
134 message
134 message
135 branch: default
135 branch: default
136 commit: (clean)
136 commit: (clean)
137 update: (current)
137 update: (current)
138 phases: 2 draft
138 phases: 2 draft
139
139
140 Test weird unicode-vs-bytes stuff
141
142 $ $PYTHON3 $HGBIN help | egrep -v '^ |^$'
143 Mercurial Distributed SCM
144 list of commands:
145 additional help topics:
146 (use 'hg help -v' to show built-in aliases and global options)
147
148 $ $PYTHON3 $HGBIN help help | egrep -v '^ |^$'
149 hg help [-ecks] [TOPIC]
150 show help for a given topic or a help overview
151 options ([+] can be repeated):
152 (some details hidden, use --verbose to show complete help)
153
140 Prove the repo is valid using the Python 2 `hg`:
154 Prove the repo is valid using the Python 2 `hg`:
141 $ hg verify
155 $ hg verify
142 checking changesets
156 checking changesets
143 checking manifests
157 checking manifests
144 crosschecking files in changesets and manifests
158 crosschecking files in changesets and manifests
145 checking files
159 checking files
146 2 files, 2 changesets, 2 total revisions
160 2 files, 2 changesets, 2 total revisions
147 $ hg log
161 $ hg log
148 changeset: 1:e1e9167203d4
162 changeset: 1:e1e9167203d4
149 tag: tip
163 tag: tip
150 user: test
164 user: test
151 date: Thu Jan 01 00:00:00 1970 +0000
165 date: Thu Jan 01 00:00:00 1970 +0000
152 summary: message
166 summary: message
153
167
154 changeset: 0:71c96e924262
168 changeset: 0:71c96e924262
155 user: test
169 user: test
156 date: Thu Jan 01 00:00:00 1970 +0000
170 date: Thu Jan 01 00:00:00 1970 +0000
157 summary: commit performed in Python 3
171 summary: commit performed in Python 3
158
172
159
173
160 $ hg log -G
174 $ hg log -G
161 @ changeset: 1:e1e9167203d4
175 @ changeset: 1:e1e9167203d4
162 | tag: tip
176 | tag: tip
163 | user: test
177 | user: test
164 | date: Thu Jan 01 00:00:00 1970 +0000
178 | date: Thu Jan 01 00:00:00 1970 +0000
165 | summary: message
179 | summary: message
166 |
180 |
167 o changeset: 0:71c96e924262
181 o changeset: 0:71c96e924262
168 user: test
182 user: test
169 date: Thu Jan 01 00:00:00 1970 +0000
183 date: Thu Jan 01 00:00:00 1970 +0000
170 summary: commit performed in Python 3
184 summary: commit performed in Python 3
171
185
172 $ hg log -Tjson
186 $ hg log -Tjson
173 [
187 [
174 {
188 {
175 "rev": 1,
189 "rev": 1,
176 "node": "e1e9167203d450ca2f558af628955b5f5afd4489",
190 "node": "e1e9167203d450ca2f558af628955b5f5afd4489",
177 "branch": "default",
191 "branch": "default",
178 "phase": "draft",
192 "phase": "draft",
179 "user": "test",
193 "user": "test",
180 "date": [0, 0],
194 "date": [0, 0],
181 "desc": "message",
195 "desc": "message",
182 "bookmarks": [],
196 "bookmarks": [],
183 "tags": ["tip"],
197 "tags": ["tip"],
184 "parents": ["71c96e924262969ff0d8d3d695b0f75412ccc3d8"]
198 "parents": ["71c96e924262969ff0d8d3d695b0f75412ccc3d8"]
185 },
199 },
186 {
200 {
187 "rev": 0,
201 "rev": 0,
188 "node": "71c96e924262969ff0d8d3d695b0f75412ccc3d8",
202 "node": "71c96e924262969ff0d8d3d695b0f75412ccc3d8",
189 "branch": "default",
203 "branch": "default",
190 "phase": "draft",
204 "phase": "draft",
191 "user": "test",
205 "user": "test",
192 "date": [0, 0],
206 "date": [0, 0],
193 "desc": "commit performed in Python 3",
207 "desc": "commit performed in Python 3",
194 "bookmarks": [],
208 "bookmarks": [],
195 "tags": [],
209 "tags": [],
196 "parents": ["0000000000000000000000000000000000000000"]
210 "parents": ["0000000000000000000000000000000000000000"]
197 }
211 }
198 ]
212 ]
General Comments 0
You need to be logged in to leave comments. Login now