Skip to content

Commit

Permalink
cleaned up eval.py
Browse files Browse the repository at this point in the history
  • Loading branch information
nfearnley committed May 21, 2024
1 parent 143d5fb commit 08416de
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 83 deletions.
6 changes: 3 additions & 3 deletions sizebot/cogs/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from sizebot.lib import utils
from sizebot.lib.constants import emojis
from sizebot.lib.eval import runEval
from sizebot.lib.eval import run_eval
from sizebot.lib.types import BotContext


Expand Down Expand Up @@ -36,7 +36,7 @@ async def eval(self, ctx: BotContext, *, evalStr: str):

async with ctx.typing():
try:
result = await runEval(ctx, evalStr)
result = await run_eval(ctx, evalStr)
except Exception as err:
logger.error("eval error:\n" + utils.format_traceback(err))
await ctx.send(emojis.warning + f" ` {format_error(err)} `")
Expand Down Expand Up @@ -69,7 +69,7 @@ async def evil(self, ctx: BotContext, *, evalStr: str):

async with ctx.typing():
try:
await runEval(ctx, evalStr)
await run_eval(ctx, evalStr)
except Exception as err:
logger.error("eval error:\n" + utils.format_traceback(err))
await ctx.author.send(emojis.warning + f" ` {format_error(err)} `")
Expand Down
147 changes: 67 additions & 80 deletions sizebot/lib/eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,59 +34,7 @@
logger = logging.getLogger("sizebot")


def eformat(name: str, value: Any) -> str:
if value is None:
emojiType = "❓"
elif callable(value):
emojiType = "πŸš™"
elif isinstance(value, (list, tuple)):
emojiType = "πŸ—’οΈ"
elif isinstance(value, set):
emojiType = "πŸ“˜"
elif isinstance(value, dict):
emojiType = "πŸ“—"
elif isinstance(value, bool):
if value:
emojiType = "βœ…"
else:
emojiType = "❎"
elif isinstance(value, (int, float)):
emojiType = "πŸ’―"
elif isinstance(value, str):
emojiType = "✏️"
elif isinstance(value, discord.member.Member):
emojiType = "πŸ‘₯"
elif isinstance(value, discord.user.User):
emojiType = "πŸ‘€"
elif isinstance(value, commands.Bot):
emojiType = "πŸ€–"
elif isinstance(value, commands.cog.Cog):
emojiType = "βš™οΈ"
elif isinstance(value, SV):
emojiType = "πŸ‡Έ"
elif isinstance(value, WV):
emojiType = "πŸ‡Ό"
elif isinstance(value, TV):
emojiType = "πŸ‡Ή"
elif isinstance(value, arrow.arrow.Arrow):
emojiType = "🏹"
else:
emojiType = "▫️"
return f"{emojiType} {name}"


def edir(o: Any) -> Embed:
"""send embed of an object's attributes, with type notation"""
e = Embed(title=utils.get_fullname(o))
attrs = [eformat(n, v) for n, v in ddir(o).items()]
pageLen = math.ceil(len(attrs) / 3)
for page in utils.chunk_list(attrs, pageLen):
e.add_field(value="\n".join(page))
return e


# TODO: CamelCase
def cachedCopy(fn: Callable) -> Callable:
def _cached_copy(fn: Callable) -> Callable:
"""Decorator that calls the wrapper function the first time it's called, and returns copies of the cached result on all later calls"""
isCached = False
r = None
Expand All @@ -102,9 +50,8 @@ def wrapper(*args, **kwargs) -> Any:
return wrapper


# TODO: CamelCase
@cachedCopy
def getEvalGlobals() -> dict[str, Any]:
@_cached_copy
def get_eval_globals() -> dict[str, Any]:
"""Construct a globals dict for eval"""
# Create a dict of builtins, excluding any in the blacklist
blacklist = [
Expand All @@ -122,10 +69,10 @@ def getEvalGlobals() -> dict[str, Any]:
"super",
"__import__"
]
evalBuiltins = {n: (v if n not in blacklist else None) for n, v in vars(builtins).items()}
eval_builtins = {n: (v if n not in blacklist else None) for n, v in vars(builtins).items()}

evalGlobals = {
"__builtins__": evalBuiltins,
eval_globals = {
"__builtins__": eval_builtins,
"inspect": inspect,
"help": str_help,
"Decimal": Decimal,
Expand All @@ -146,7 +93,6 @@ def getEvalGlobals() -> dict[str, Any]:
"emojis": emojis,
"itertools": itertools,
"conf": conf,
"findOne": find_one,
"datetime": datetime,
"date": date,
"time": time,
Expand All @@ -163,14 +109,13 @@ def getEvalGlobals() -> dict[str, Any]:
"errors": errors,
"arrow": arrow,
"roll": roll,
"_evalmath": _evalmath
"evalmath": _evalmath
}

return evalGlobals
return eval_globals


# TODO: CamelCase
def buildEvalWrapper(evalStr: str, addReturn: bool = True) -> tuple[CodeType, str]:
def _build_eval_wrapper(evalStr: str, addReturn: bool = True) -> tuple[CodeType, str]:
"""Build a wrapping async function that lets the eval command run multiple lines, and return the result of the last line"""
evalLines = evalStr.rstrip().split("\n")
if evalLines[-1].startswith(" "):
Expand All @@ -183,21 +128,20 @@ def buildEvalWrapper(evalStr: str, addReturn: bool = True) -> tuple[CodeType, st
except SyntaxError:
# If we get a syntax error, maybe it's because someone is trying to do an assignment on the last line? Might as well try it without a return statement and see if it works.
if addReturn:
return buildEvalWrapper(evalStr, False)
return _build_eval_wrapper(evalStr, False)
raise

return evalWrapper, evalWrapperStr


# TODO: CamelCase
async def runEval(ctx: BotContext, evalStr: str) -> Any:
evalGlobals = getEvalGlobals()
async def run_eval(ctx: BotContext, evalStr: str) -> Any:
evalGlobals = get_eval_globals()
evalLocals = {}

# Add ctx to the globals
evalGlobals["ctx"] = ctx

evalWrapper, evalWrapperStr = buildEvalWrapper(evalStr)
evalWrapper, evalWrapperStr = _build_eval_wrapper(evalStr)

logger.debug(f"Executing eval:\n{evalWrapperStr}")

Expand All @@ -211,7 +155,58 @@ async def runEval(ctx: BotContext, evalStr: str) -> Any:
return await evalFn()


def pformat(name: str, value: Any) -> str:
def _eformat(name: str, value: Any) -> str:
if value is None:
emojiType = "❓"
elif callable(value):
emojiType = "πŸš™"
elif isinstance(value, (list, tuple)):
emojiType = "πŸ—’οΈ"
elif isinstance(value, set):
emojiType = "πŸ“˜"
elif isinstance(value, dict):
emojiType = "πŸ“—"
elif isinstance(value, bool):
if value:
emojiType = "βœ…"
else:
emojiType = "❎"
elif isinstance(value, (int, float)):
emojiType = "πŸ’―"
elif isinstance(value, str):
emojiType = "✏️"
elif isinstance(value, discord.member.Member):
emojiType = "πŸ‘₯"
elif isinstance(value, discord.user.User):
emojiType = "πŸ‘€"
elif isinstance(value, commands.Bot):
emojiType = "πŸ€–"
elif isinstance(value, commands.cog.Cog):
emojiType = "βš™οΈ"
elif isinstance(value, SV):
emojiType = "πŸ‡Έ"
elif isinstance(value, WV):
emojiType = "πŸ‡Ό"
elif isinstance(value, TV):
emojiType = "πŸ‡Ή"
elif isinstance(value, arrow.arrow.Arrow):
emojiType = "🏹"
else:
emojiType = "▫️"
return f"{emojiType} {name}"


def edir(o: Any) -> Embed:
"""send embed of an object's attributes, with type notation"""
e = Embed(title=utils.get_fullname(o))
attrs = [_eformat(n, v) for n, v in _ddir(o).items()]
pageLen = math.ceil(len(attrs) / 3)
for page in utils.chunk_list(attrs, pageLen):
e.add_field(value="\n".join(page))
return e


def _pformat(name: str, value: Any) -> str:
if value is None:
return f"{name}?"
if callable(value):
Expand All @@ -227,21 +222,13 @@ def pformat(name: str, value: Any) -> str:

def pdir(o: Any) -> list:
"""return a list of an object's attributes, with type notation."""
return [pformat(n, v) for n, v in ddir(o).items()]
return [_pformat(n, v) for n, v in _ddir(o).items()]


def ddir(o: Any) -> dict:
def _ddir(o: Any) -> dict:
"""return a dictionary of an object's attributes."""
return {n: v for n, v in inspect.getmembers(o) if not n.startswith("_")}


def find_one(iterator: Iterator) -> Any | None:
try:
val = next(iterator)
except StopIteration:
val = None
return val


def str_help(topic: str) -> str:
return pydoc.plain(pydoc.render_doc(topic))

0 comments on commit 08416de

Please sign in to comment.