# -*- coding: utf-8 -*-
"""
jishaku.paginators
~~~~~~~~~~~~~~~~~~
Paginator-related tools and interfaces for Jishaku.
:copyright: (c) 2021 Devon (Gorialis) R
:license: MIT, see LICENSE for more details.
"""
import discord
from discord.ext import commands
from jishaku.flags import Flags
from jishaku.hljs import get_language, guess_file_traits
from jishaku.shim.paginator_base import EmojiSettings
# Version detection
if discord.version_info >= (2, 0, 0):
from jishaku.shim.paginator_200 import PaginatorEmbedInterface, PaginatorInterface
else:
from jishaku.shim.paginator_170 import PaginatorEmbedInterface, PaginatorInterface
__all__ = (
"EmojiSettings",
"PaginatorInterface",
"PaginatorEmbedInterface",
"WrappedPaginator",
"FilePaginator",
"use_file_check",
)
[docs]class WrappedPaginator(commands.Paginator):
"""
A paginator that allows automatic wrapping of lines should they not fit.
This is useful when paginating unpredictable output,
as it allows for line splitting on big chunks of data.
Delimiters are prioritized in the order of their tuple.
Parameters
-----------
wrap_on: tuple
A tuple of wrapping delimiters.
include_wrapped: bool
Whether to include the delimiter at the start of the new wrapped line.
force_wrap: bool
If this is True, lines will be split at their maximum points should trimming not be possible
with any provided delimiter.
"""
def __init__(
self,
*args,
wrap_on=("\n", " "),
include_wrapped=True,
force_wrap=False,
**kwargs,
):
super().__init__(*args, **kwargs)
self.wrap_on = wrap_on
self.include_wrapped = include_wrapped
self.force_wrap = force_wrap
[docs] def add_line(self, line="", *, empty=False):
true_max_size = self.max_size - self._prefix_len - self._suffix_len - 2
original_length = len(line)
while len(line) > true_max_size:
search_string = line[0 : true_max_size - 1]
wrapped = False
for delimiter in self.wrap_on:
position = search_string.rfind(delimiter)
if position > 0:
super().add_line(line[0:position], empty=empty)
wrapped = True
if self.include_wrapped:
line = line[position:]
else:
line = line[position + len(delimiter) :]
break
if not wrapped:
if self.force_wrap:
super().add_line(line[0 : true_max_size - 1])
line = line[true_max_size - 1 :]
else:
raise ValueError(
f"Line of length {original_length} had sequence of {len(line)} characters"
f" (max is {true_max_size}) that WrappedPaginator could not wrap with"
f" delimiters: {self.wrap_on}"
)
super().add_line(line, empty=empty)
[docs]class FilePaginator(commands.Paginator):
"""
A paginator of syntax-highlighted codeblocks, read from a file-like.
Parameters
-----------
fp
A file-like (implements ``fp.read``) to read the data for this paginator from.
line_span: Optional[Tuple[int, int]]
A linespan to read from the file. If None, reads the whole file.
language_hints: Tuple[str]
A tuple of strings that may hint to the language of this file.
This could include filenames, MIME types, or shebangs.
A shebang present in the actual file will always be prioritized over this.
"""
def __init__(self, fp, line_span=None, language_hints=(), **kwargs):
language = ""
for hint in language_hints:
language = get_language(hint)
if language:
break
if not language:
try:
language = get_language(fp.name)
except AttributeError:
pass
content, _, file_language = guess_file_traits(fp.read())
language = file_language or language
lines = content.split("\n")
super().__init__(prefix=f"```{language}", suffix="```", **kwargs)
if line_span:
line_span = sorted(line_span)
if min(line_span) < 1 or max(line_span) > len(lines):
raise ValueError("Linespan goes out of bounds.")
lines = lines[line_span[0] - 1 : line_span[1]]
for line in lines:
self.add_line(line)
class WrappedFilePaginator(FilePaginator, WrappedPaginator):
"""
Combination of FilePaginator and WrappedPaginator.
In other words, a FilePaginator that supports line wrapping.
"""
def use_file_check(ctx: commands.Context, size: int) -> bool:
"""
A check to determine if uploading a file and relying on Discord's file preview is acceptable over a PaginatorInterface.
"""
return all(
[
size < 50_000, # Check the text is below the Discord cutoff point;
not Flags.FORCE_PAGINATOR, # Check the user hasn't explicitly disabled this;
(not ctx.author.is_on_mobile() if ctx.guild and ctx.bot.intents.presences else True), # Ensure the user isn't on mobile
]
)