Fancier IPython-like eval command#3
Conversation
Martmists-GH
commented
Feb 9, 2018
- Supports Embeds
- Pretty-print
- Shortens if too long
- Supports catching stdout
- Supports async code
- Automatically awaits awaitables if they are the return value
- Some other misc changes
- Supports Embeds - Pretty-prints - Shortens - Supports catching stdout
gdude2002
left a comment
There was a problem hiding this comment.
You've got a bunch of linting errors.
./bot/cogs/eval.py:12:1: F401 'discord.ext.commands.Context' imported but unused
./bot/cogs/eval.py:140:0: P101 format string does contain unindexed parameters
./bot/cogs/eval.py:143:1: B102 Use of exec detected.
./bot/cogs/eval.py:171:1: E303 too many blank lines (3)
./bot/cogs/eval.py:22:1: W293 blank line contains whitespace
./bot/cogs/eval.py:30:23: F821 undefined name 'io'
./bot/cogs/eval.py:33:1: W293 blank line contains whitespace
./bot/cogs/eval.py:4:1: F401 'io.StringIO' imported but unused
./bot/cogs/eval.py:5:1: I100 Import statements are in the wrong order. import inspect should be before from io
./bot/cogs/eval.py:65:23: F821 undefined name 'io'
./bot/cogs/eval.py:7:1: I100 Import statements are in the wrong order. import pprint should be before import textwrap
./bot/cogs/eval.py:9:1: I100 Import statements are in the wrong order. import re should be before import traceback
| email: | ||
| on_success: never | ||
| on_failure: never No newline at end of file | ||
| on_failure: never |
There was a problem hiding this comment.
This file is out of scope of the PR, please remove it.
There was a problem hiding this comment.
I don't even know where that line came from, probably my auto-travis-lint?
| elif code.startswith("`") and code.endswith("`"): | ||
| code = code[1:-1] | ||
| # Indent the 3 dots correctly | ||
| s = (f"{{:<{len(str(self.ln))+2}}}...: ").format("") |
There was a problem hiding this comment.
Can you comment this better? It's very unclear what this would do for a beginner
| _code = """ | ||
| async def func(): | ||
| try: | ||
| with contextlib.redirect_stdout(self.stdout): |
There was a problem hiding this comment.
Will doing this mess up printing stuff to stdout from the rest of the bot while the eval is being processed?
There was a problem hiding this comment.
Contextlib should handle that. I've personally never had such issue.
| # far enough to align them. | ||
| # we first `str()` the line number | ||
| # then we get the length | ||
| # and do a simple {:<LENGHT} |
|
This is from one of your other bots originally, right? What's the license on that bot? |
|
|
The license on said bot makes contributors unable to share it without permission, and I'm giving permission by adding it myself. I will fix those issues in a second. |
|
Alright, that's fine. Really the only other thing I'd like to address in this PR is readability - there's lots of overly-short variable names, one-liners and somewhat opaque string concatenations. We'd prefer if you could deal with that, but we can otherwise try to do it ourselves. |
lemonsaurus
left a comment
There was a problem hiding this comment.
The code is good, but readability is quite poor. I'd like to see the readability improved before we merge this. there are too many single-letter variable names, too many convoluted one-liners, too much nesting.. If we can get a version of this that follows the Zen of Python, I'd probably be willing to merge it.
| elif code.startswith("`") and code.endswith("`"): | ||
| code = code[1:-1] | ||
| # Indent the 3 dots correctly | ||
| s = (f"{{:<{len(str(self.ln))+2}}}...: ").format("") |
| if len(lines) != 1: | ||
| lines += [""] | ||
|
|
||
| # Create the inpit dialog |
| # Create the inpit dialog | ||
| for i, line in enumerate(lines): | ||
| if i == 0: | ||
| s = f"In [{self.ln}]: " |
There was a problem hiding this comment.
single letter variable names
| if line.startswith("return"): | ||
| line = line[6:].strip() | ||
|
|
||
| res += s + line + "\n" |
There was a problem hiding this comment.
convoluted string concatenation
There was a problem hiding this comment.
Using f-strings or str.format here would seem unneeded to me.
There was a problem hiding this comment.
if res and s had better variable names, it would probably be okay.
lemonsaurus
left a comment
There was a problem hiding this comment.
some more readability concerns.
| func = self.env['func'] | ||
| res = await func() | ||
|
|
||
| except: # noqa pylint: disable=bare-except |
There was a problem hiding this comment.
would bare-except trigger if this was except Exception:? if not, isn't that better than disabling a pylint rule?
| if pretty.count("\n") > 20: | ||
| # Text too long, shorten | ||
| li = pretty.split("\n") | ||
| pretty = "\n".join(li[:3]) + "\n ...\n" + "\n".join(li[-3:]) |
| inp = inp[4:] | ||
|
|
||
| code = string.strip() | ||
| lines = [l for l in inp.split("\n") if l.strip()] |
There was a problem hiding this comment.
for this to be beginner friendly, listcomps should have an accompanying comment. the meaning of the if l.strip() in this context may not be immediately obvious.
|
|
||
| self.interpreter = Interpreter(bot) | ||
|
|
||
| def _format(self, inp, out): |
There was a problem hiding this comment.
this function needs a docstring so it will be obvious what exactly it's used for.
| if line.startswith("return"): | ||
| line = line[6:].strip() | ||
|
|
||
| res += s + line + "\n" |
There was a problem hiding this comment.
if res and s had better variable names, it would probably be okay.
| if len(lines) != 1: | ||
| lines += [""] | ||
|
|
||
| # Create the inpit dialog |
|
|
||
| res += s + line + "\n" | ||
|
|
||
| self.stdout.seek(0) |
There was a problem hiding this comment.
maybe block comment this part to explain why you're doing it. generally speaking, the code could use many such comments explaining the flow of this method. Ideally we want people who are moderately new to python to be able to skim through this method and understand the flow without necessarily understanding all the code.
| if (isinstance(out, str) and | ||
| out.startswith("Traceback (most recent call last):\n")): | ||
| # Leave out the traceback message | ||
| out = "\n" + "\n".join(out.split("\n")[1:]) |
There was a problem hiding this comment.
it's got string concatenation, a .split nested inside a .join, and a container slice, all in one line. it's too dense. it'd be better to split this into more lines if it can't be simplified.
| out = "\n" + "\n".join(out.split("\n")[1:]) | ||
|
|
||
| pretty = (pprint.pformat(out, compact=True, width=60) | ||
| if not isinstance(out, str) else str(out)) |
There was a problem hiding this comment.
readability suffers because of the ternary - instead of reading the logic in semantic order (if this then that), the reader has to parse it as (that if this). And why do we need to cast out to a str when the isinstance clearly establishes that it is already a str?
the far simpler
if not isinstance(out, str):
pretty = pprint.pformat(out, compact=True, width=60)
else:
pretty = out
would be better.
|
for the record, I've left comments for absolutely all my readability concerns now. if they were all adressed, I'd approve. |
|
Has there been any movement on this? |
|
Not yet, it's been a busy week so far. I'll probably push a fix tomorrow |
|
That's a lot better. Thanks! |
|
This is a considerable improvement. Good work. |
Merge pull request #1028 from dolphingarlic/master
This commit should be squashed upon PR merge. - Another fix in `windows-path.md`
This commit should be squashed upon PR merge. - Another fix in `windows-path.md`
This commit should be squashed upon PR merge. - Another fix in `windows-path.md`