comparison mercurial/ui.py @ 49795:f1e820cda2f5

typing: add type hints related to message output in mercurial/ui.py This will shake loose some bytes vs str issues in the doc checker.
author Matt Harbison <matt_harbison@yahoo.com>
date Sat, 10 Dec 2022 14:44:46 -0500
parents 25fe689a4a64
children 0449fb7729d7
comparison
equal deleted inserted replaced
49794:25fe689a4a64 49795:f1e820cda2f5
18 import subprocess 18 import subprocess
19 import sys 19 import sys
20 import traceback 20 import traceback
21 21
22 from typing import ( 22 from typing import (
23 Dict,
24 List,
23 Optional, 25 Optional,
26 Tuple,
27 Union,
28 cast,
24 ) 29 )
25 30
26 from .i18n import _ 31 from .i18n import _
27 from .node import hex 32 from .node import hex
28 from .pycompat import ( 33 from .pycompat import (
49 procutil, 54 procutil,
50 resourceutil, 55 resourceutil,
51 stringutil, 56 stringutil,
52 urlutil, 57 urlutil,
53 ) 58 )
59
60 # The **opts args of the various write() methods can be basically anything, but
61 # there's no way to express it as "anything but str". So type it to be the
62 # handful of known types that are used.
63 _MsgOpts = Union[bytes, bool, List["_PromptChoice"]]
64 _PromptChoice = Tuple[bytes, bytes]
54 65
55 urlreq = util.urlreq 66 urlreq = util.urlreq
56 67
57 # for use with str.translate(None, _keepalnum), to keep just alphanumerics 68 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
58 _keepalnum = b''.join( 69 _keepalnum = b''.join(
1202 def canbatchlabeledwrites(self): 1213 def canbatchlabeledwrites(self):
1203 '''check if write calls with labels are batchable''' 1214 '''check if write calls with labels are batchable'''
1204 # Windows color printing is special, see ``write``. 1215 # Windows color printing is special, see ``write``.
1205 return self._colormode != b'win32' 1216 return self._colormode != b'win32'
1206 1217
1207 def write(self, *args, **opts): 1218 def write(self, *args: bytes, **opts: _MsgOpts) -> None:
1208 """write args to output 1219 """write args to output
1209 1220
1210 By default, this method simply writes to the buffer or stdout. 1221 By default, this method simply writes to the buffer or stdout.
1211 Color mode can be set on the UI class to have the output decorated 1222 Color mode can be set on the UI class to have the output decorated
1212 with color modifier before being written to stdout. 1223 with color modifier before being written to stdout.
1260 finally: 1271 finally:
1261 self._blockedtimes[b'stdio_blocked'] += ( 1272 self._blockedtimes[b'stdio_blocked'] += (
1262 util.timer() - starttime 1273 util.timer() - starttime
1263 ) * 1000 1274 ) * 1000
1264 1275
1265 def write_err(self, *args, **opts): 1276 def write_err(self, *args: bytes, **opts: _MsgOpts) -> None:
1266 self._write(self._ferr, *args, **opts) 1277 self._write(self._ferr, *args, **opts)
1267 1278
1268 def _write(self, dest, *args, **opts): 1279 def _write(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1269 # update write() as well if you touch this code 1280 # update write() as well if you touch this code
1270 if self._isbuffered(dest): 1281 if self._isbuffered(dest):
1271 label = opts.get('label', b'') 1282 label = opts.get('label', b'')
1272 if label and self._bufferapplylabels: 1283 if label and self._bufferapplylabels:
1273 self._buffers[-1].extend(self.label(a, label) for a in args) 1284 self._buffers[-1].extend(self.label(a, label) for a in args)
1274 else: 1285 else:
1275 self._buffers[-1].extend(args) 1286 self._buffers[-1].extend(args)
1276 else: 1287 else:
1277 self._writenobuf(dest, *args, **opts) 1288 self._writenobuf(dest, *args, **opts)
1278 1289
1279 def _writenobuf(self, dest, *args, **opts): 1290 def _writenobuf(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1280 # update write() as well if you touch this code 1291 # update write() as well if you touch this code
1281 if not opts.get('keepprogressbar', False): 1292 if not opts.get('keepprogressbar', False):
1282 self._progclear() 1293 self._progclear()
1283 msg = b''.join(args) 1294 msg = b''.join(args)
1284 1295
1316 finally: 1327 finally:
1317 self._blockedtimes[b'stdio_blocked'] += ( 1328 self._blockedtimes[b'stdio_blocked'] += (
1318 util.timer() - starttime 1329 util.timer() - starttime
1319 ) * 1000 1330 ) * 1000
1320 1331
1321 def _writemsg(self, dest, *args, **opts): 1332 def _writemsg(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1322 timestamp = self.showtimestamp and opts.get('type') in { 1333 timestamp = self.showtimestamp and opts.get('type') in {
1323 b'debug', 1334 b'debug',
1324 b'error', 1335 b'error',
1325 b'note', 1336 b'note',
1326 b'status', 1337 b'status',
1333 ) + args 1344 ) + args
1334 _writemsgwith(self._write, dest, *args, **opts) 1345 _writemsgwith(self._write, dest, *args, **opts)
1335 if timestamp: 1346 if timestamp:
1336 dest.flush() 1347 dest.flush()
1337 1348
1338 def _writemsgnobuf(self, dest, *args, **opts): 1349 def _writemsgnobuf(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1339 _writemsgwith(self._writenobuf, dest, *args, **opts) 1350 _writemsgwith(self._writenobuf, dest, *args, **opts)
1340 1351
1341 def flush(self): 1352 def flush(self) -> None:
1342 # opencode timeblockedsection because this is a critical path 1353 # opencode timeblockedsection because this is a critical path
1343 starttime = util.timer() 1354 starttime = util.timer()
1344 try: 1355 try:
1345 try: 1356 try:
1346 self._fout.flush() 1357 self._fout.flush()
1695 # usually those are non-interactive 1706 # usually those are non-interactive
1696 return self._isatty(self._fout) 1707 return self._isatty(self._fout)
1697 1708
1698 return i 1709 return i
1699 1710
1700 def _readline(self, prompt=b' ', promptopts=None): 1711 def _readline(
1712 self,
1713 prompt: bytes = b' ',
1714 promptopts: Optional[Dict[str, _MsgOpts]] = None,
1715 ) -> bytes:
1701 # Replacing stdin/stdout temporarily is a hard problem on Python 3 1716 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1702 # because they have to be text streams with *no buffering*. Instead, 1717 # because they have to be text streams with *no buffering*. Instead,
1703 # we use rawinput() only if call_readline() will be invoked by 1718 # we use rawinput() only if call_readline() will be invoked by
1704 # PyOS_Readline(), so no I/O will be made at Python layer. 1719 # PyOS_Readline(), so no I/O will be made at Python layer.
1705 usereadline = ( 1720 usereadline = (
1777 return r 1792 return r
1778 except EOFError: 1793 except EOFError:
1779 raise error.ResponseExpected() 1794 raise error.ResponseExpected()
1780 1795
1781 @staticmethod 1796 @staticmethod
1782 def extractchoices(prompt): 1797 def extractchoices(prompt: bytes) -> Tuple[bytes, List[_PromptChoice]]:
1783 """Extract prompt message and list of choices from specified prompt. 1798 """Extract prompt message and list of choices from specified prompt.
1784 1799
1785 This returns tuple "(message, choices)", and "choices" is the 1800 This returns tuple "(message, choices)", and "choices" is the
1786 list of tuple "(response character, text without &)". 1801 list of tuple "(response character, text without &)".
1787 1802
1809 ampidx = s.index(b'&') 1824 ampidx = s.index(b'&')
1810 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1) 1825 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1811 1826
1812 return (msg, [choicetuple(s) for s in choices]) 1827 return (msg, [choicetuple(s) for s in choices])
1813 1828
1814 def promptchoice(self, prompt, default=0): 1829 def promptchoice(self, prompt: bytes, default: int = 0) -> int:
1815 """Prompt user with a message, read response, and ensure it matches 1830 """Prompt user with a message, read response, and ensure it matches
1816 one of the provided choices. The prompt is formatted as follows: 1831 one of the provided choices. The prompt is formatted as follows:
1817 1832
1818 "would you like fries with that (Yn)? $$ &Yes $$ &No" 1833 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1819 1834
1829 if r.lower() in resps: 1844 if r.lower() in resps:
1830 return resps.index(r.lower()) 1845 return resps.index(r.lower())
1831 # TODO: shouldn't it be a warning? 1846 # TODO: shouldn't it be a warning?
1832 self._writemsg(self._fmsgout, _(b"unrecognized response\n")) 1847 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1833 1848
1834 def getpass(self, prompt=None, default=None): 1849 def getpass(
1850 self, prompt: Optional[bytes] = None, default: Optional[bytes] = None
1851 ) -> Optional[bytes]:
1835 if not self.interactive(): 1852 if not self.interactive():
1836 return default 1853 return default
1837 try: 1854 try:
1838 self._writemsg( 1855 self._writemsg(
1839 self._fmsgerr, 1856 self._fmsgerr,
1852 else: 1869 else:
1853 return util.get_password() 1870 return util.get_password()
1854 except EOFError: 1871 except EOFError:
1855 raise error.ResponseExpected() 1872 raise error.ResponseExpected()
1856 1873
1857 def status(self, *msg, **opts): 1874 def status(self, *msg: bytes, **opts: _MsgOpts) -> None:
1858 """write status message to output (if ui.quiet is False) 1875 """write status message to output (if ui.quiet is False)
1859 1876
1860 This adds an output label of "ui.status". 1877 This adds an output label of "ui.status".
1861 """ 1878 """
1862 if not self.quiet: 1879 if not self.quiet:
1863 self._writemsg(self._fmsgout, type=b'status', *msg, **opts) 1880 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1864 1881
1865 def warn(self, *msg, **opts): 1882 def warn(self, *msg: bytes, **opts: _MsgOpts) -> None:
1866 """write warning message to output (stderr) 1883 """write warning message to output (stderr)
1867 1884
1868 This adds an output label of "ui.warning". 1885 This adds an output label of "ui.warning".
1869 """ 1886 """
1870 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts) 1887 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1871 1888
1872 def error(self, *msg, **opts): 1889 def error(self, *msg: bytes, **opts: _MsgOpts) -> None:
1873 """write error message to output (stderr) 1890 """write error message to output (stderr)
1874 1891
1875 This adds an output label of "ui.error". 1892 This adds an output label of "ui.error".
1876 """ 1893 """
1877 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts) 1894 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1878 1895
1879 def note(self, *msg, **opts): 1896 def note(self, *msg: bytes, **opts: _MsgOpts) -> None:
1880 """write note to output (if ui.verbose is True) 1897 """write note to output (if ui.verbose is True)
1881 1898
1882 This adds an output label of "ui.note". 1899 This adds an output label of "ui.note".
1883 """ 1900 """
1884 if self.verbose: 1901 if self.verbose:
1885 self._writemsg(self._fmsgout, type=b'note', *msg, **opts) 1902 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1886 1903
1887 def debug(self, *msg, **opts): 1904 def debug(self, *msg: bytes, **opts: _MsgOpts) -> None:
1888 """write debug message to output (if ui.debugflag is True) 1905 """write debug message to output (if ui.debugflag is True)
1889 1906
1890 This adds an output label of "ui.debug". 1907 This adds an output label of "ui.debug".
1891 """ 1908 """
1892 if self.debugflag: 1909 if self.debugflag:
2146 for logger in activeloggers: 2163 for logger in activeloggers:
2147 logger.log(self, event, msg, opts) 2164 logger.log(self, event, msg, opts)
2148 finally: 2165 finally:
2149 self._loggers = registeredloggers 2166 self._loggers = registeredloggers
2150 2167
2151 def label(self, msg, label): 2168 def label(self, msg: bytes, label: bytes) -> bytes:
2152 """style msg based on supplied label 2169 """style msg based on supplied label
2153 2170
2154 If some color mode is enabled, this will add the necessary control 2171 If some color mode is enabled, this will add the necessary control
2155 characters to apply such color. In addition, 'debug' color mode adds 2172 characters to apply such color. In addition, 'debug' color mode adds
2156 markup showing which label affects a piece of text. 2173 markup showing which label affects a piece of text.
2160 """ 2177 """
2161 if self._colormode is not None: 2178 if self._colormode is not None:
2162 return color.colorlabel(self, msg, label) 2179 return color.colorlabel(self, msg, label)
2163 return msg 2180 return msg
2164 2181
2165 def develwarn(self, msg, stacklevel=1, config=None): 2182 def develwarn(
2183 self, msg: bytes, stacklevel: int = 1, config: Optional[bytes] = None
2184 ) -> None:
2166 """issue a developer warning message 2185 """issue a developer warning message
2167 2186
2168 Use 'stacklevel' to report the offender some layers further up in the 2187 Use 'stacklevel' to report the offender some layers further up in the
2169 stack. 2188 stack.
2170 """ 2189 """
2192 2211
2193 # avoid cycles 2212 # avoid cycles
2194 del curframe 2213 del curframe
2195 del calframe 2214 del calframe
2196 2215
2197 def deprecwarn(self, msg, version, stacklevel=2): 2216 def deprecwarn(
2217 self, msg: bytes, version: bytes, stacklevel: int = 2
2218 ) -> None:
2198 """issue a deprecation warning 2219 """issue a deprecation warning
2199 2220
2200 - msg: message explaining what is deprecated and how to upgrade, 2221 - msg: message explaining what is deprecated and how to upgrade,
2201 - version: last version where the API will be supported, 2222 - version: last version where the API will be supported,
2202 """ 2223 """
2285 if name == b'stderr': 2306 if name == b'stderr':
2286 return ui.ferr, ui.ferr 2307 return ui.ferr, ui.ferr
2287 raise error.Abort(b'invalid ui.message-output destination: %s' % name) 2308 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2288 2309
2289 2310
2290 def _writemsgwith(write, dest, *args, **opts): 2311 def _writemsgwith(write, dest, *args: bytes, **opts: _MsgOpts) -> None:
2291 """Write ui message with the given ui._write*() function 2312 """Write ui message with the given ui._write*() function
2292 2313
2293 The specified message type is translated to 'ui.<type>' label if the dest 2314 The specified message type is translated to 'ui.<type>' label if the dest
2294 isn't a structured channel, so that the message will be colorized. 2315 isn't a structured channel, so that the message will be colorized.
2295 """ 2316 """