File: //snap/certbot/current/lib64/python3.12/site-packages/certbot/_internal/display/util.py
"""Internal Certbot display utilities."""
import sys
import textwrap
from typing import Optional
from acme import messages as acme_messages
from certbot._internal import san
from certbot.compat import misc
def wrap_lines(msg: str) -> str:
"""Format lines nicely to 80 chars.
:param str msg: Original message
:returns: Formatted message respecting newlines in message
:rtype: str
"""
lines = msg.splitlines()
fixed_l = []
for line in lines:
fixed_l.append(textwrap.fill(
line,
80,
break_long_words=False,
break_on_hyphens=False))
return '\n'.join(fixed_l)
def parens_around_char(label: str) -> str:
"""Place parens around first character of label.
:param str label: Must contain at least one character
"""
return "({first}){rest}".format(first=label[0], rest=label[1:])
def input_with_timeout(prompt: Optional[str] = None, timeout: float = 36000.0) -> str:
"""Get user input with a timeout.
Behaves the same as the builtin input, however, an error is raised if
a user doesn't answer after timeout seconds. The default timeout
value was chosen to place it just under 12 hours for users following
our advice and running Certbot twice a day.
:param str prompt: prompt to provide for input
:param float timeout: maximum number of seconds to wait for input
:returns: user response
:rtype: str
:raises errors.Error if no answer is given before the timeout
"""
# use of sys.stdin and sys.stdout to mimic the builtin input based on
# https://github.com/python/cpython/blob/baf7bb30a02aabde260143136bdf5b3738a1d409/Lib/getpass.py#L129
if prompt:
sys.stdout.write(prompt)
sys.stdout.flush()
line = misc.readline_with_timeout(timeout, prompt)
if not line:
raise EOFError
return line.rstrip('\n')
def separate_list_input(input_: str) -> list[str]:
"""Separate a comma or space separated list.
:param str input_: input from the user
:returns: strings
:rtype: list
"""
no_commas = input_.replace(",", " ")
# Each string is naturally unicode, this causes problems with M2Crypto SANs
# TODO: check if above is still true when M2Crypto is gone ^
return [str(string) for string in no_commas.split()]
def summarize_sans(sans: list[san.SAN]) -> str:
"""Summarizes a list of identifiers in the format of:
example.com.com and N more
or if there are only two identifiers:
example.com and 192.0.2.77
or if there is only one identifier:
example.com
:param list sans: `san.SAN` list of domains and/or IP addresses
:returns: a summary
:rtype: str
"""
if not sans:
return ""
length = len(sans)
if length == 1:
return str(sans[0])
elif length == 2:
return f"{sans[0]} and {sans[1]}"
else:
return f"{sans[0]} and {length - 1} more"
def describe_acme_error(error: acme_messages.Error) -> str:
"""Returns a human-readable description of an RFC7807 error.
:param error: The ACME error
:returns: a string describing the error, suitable for human consumption.
:rtype: str
"""
parts = (error.title, error.detail)
if any(parts):
return ' :: '.join(part for part in parts if part is not None)
if error.description:
return error.description
return error.typ