Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some Goonstation improvements(?) #23

Open
wants to merge 10 commits into
base: ss13/master
Choose a base branch
from
93 changes: 59 additions & 34 deletions dmcompile/dmcompile.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def __init__(self, bot):
self.config = Config.get_conf(self, 32174327454, force_registration=True)

default_config = {
"listener_url": "http://localhost:5000/compile"
"listener_url": "http://localhost:5000/compile",
"default_version": "514.1589"
}

self.config.register_global(**default_config)
Expand All @@ -47,7 +48,7 @@ async def setcompile(self, ctx):
pass

@setcompile.command()
async def listener(self, ctx, url:str = None):
async def listener(self, ctx, url:str):
"""
Set the full URL for the listener

Expand All @@ -59,51 +60,60 @@ async def listener(self, ctx, url:str = None):
await ctx.send(f"Listener URL set to: {url}")
except (ValueError, KeyError, AttributeError):
await ctx.send("There was an error setting the listener's URL. Please check your entry and try again.")

@commands.command()
async def listbyond(self, ctx):
"""
List the available BYOND versions

List generated from the beestation/byond docker repository.
@setcompile.command()
async def default_version(self, ctx, version:str):
"""
Set the default version of BYOND used

_This command also updates the internal list of available versions for the compiler._
Should be in format similar to: 514.1589
"""

repo_tags = await self.version_list()
repo_tags.remove("latest")

await ctx.send(f"The currently available BYOND versions are:\n> {chat_formatting.humanize_list(repo_tags)}")
try:
await self.config.default_version.set(version)
await ctx.send(f"Default version set to: {version}")
except (ValueError, KeyError, AttributeError):
await ctx.send("There was an error setting the default version. Please check your entry and try again.")

@commands.command()
async def compile(self, ctx, version:str = "latest", *,code:str):
@commands.command(usage="[version] <code>")
async def compile(self, ctx, *, code:str):
"""
Compile and run DM code

This command will attempt to compile and execute given DM code. It will respond with the full compile log along with any outputs given during runtime. If there are any errors during compilation, the bot will respond with a list provided by DreamMaker.

The code must be contained within a codeblock, for example:
```
world.log << 'Hello world!'
```c
world.log << "Hello world!"
```
If you're using multiple functions, or if your code requires indentation, you must define a `proc/main()` as shown below.
```
```c
proc/example()
world.log << "I'm an example function!"

proc/main()
example()
```
You can also do [p]compile `expression` to evaluate and print an expression. Example [p]compile `NORTH | EAST`.

Use `listbyond` to get a list of BYOND versions you can compile with.
You can include the target BYOND version before the code block. Example: [p]compile 514.1589 `world.byond_build`
"""
if version.startswith('```'):
version = "latest"
code = f"```\n{code}"
else:
if version not in self.repo_tags:
return await ctx.send(f"That version of BYOND is not supported. Use `{ctx.prefix}listbyond` for a list of supported versions.")

tiny_output = False

version = await self.config.default_version()

code_quote_char = '```' if '```' in code else '`'
if code_quote_char not in code:
return await ctx.send("Your code has to be in a code block!")
maybe_version, code = code.split(code_quote_char, 1)
code = code_quote_char + code
maybe_version = maybe_version.strip()
if maybe_version:
version = maybe_version

if code_quote_char == '`':
tiny_output = True

code = self.cleanup_code(utils.chat_formatting.escape(code))
if code is None:
return await ctx.send("Your code has to be in a code block!")
Expand Down Expand Up @@ -132,24 +142,36 @@ async def compile(self, ctx, version:str = "latest", *,code:str):
run_log = r['run_log']

if r['timeout']:
embed = discord.Embed(title="Execution timed out (30 seconds)", description=f"Compiler Output:\n{box(escape(compile_log, mass_mentions=True, formatting=True))}\nExecution Output:\n{box(escape(run_log, mass_mentions=True, formatting=True))}", color=0xd3d3d3)
await ctx.send(embed=embed)
return await message.delete()
if tiny_output:
await ctx.send("Timed out")
return await message.delete()
else:
embed = discord.Embed(title="Execution timed out (30 seconds)", description=f"**Compiler Output:**\n{box(escape(compile_log, mass_mentions=True, formatting=True))}\n**Execution Output:**\n{box(escape(run_log, mass_mentions=True, formatting=True))}", color=0xd3d3d3)
await ctx.send(embed=embed)
return await message.delete()

errors = ERROR_PATTERN.search(compile_log)
warnings = WARNING_PATTERN.search(compile_log)
if int(errors.group(1)) > 0:
embed = discord.Embed(title="Compilation failed!", description=f"Compiler output:\n{box(escape(compile_log, mass_mentions=True, formatting=True))}", color=0xff0000)
await ctx.send(embed=embed)
content = "Maybe you meant to use \\`\\`\\` instead of \\`?" if tiny_output else None
await ctx.send(content=content, embed=embed)
return await message.delete()
elif int(warnings.group(1)) > 0:
embed = discord.Embed(title="Warnings found during compilation", description=f"Compiler Output:\n{box(escape(compile_log, mass_mentions=True, formatting=True))}\nExecution Output:\n{box(escape(run_log, mass_mentions=True, formatting=True))}", color=0xffcc00)
embed = discord.Embed(title="Warnings found during compilation", description=f"**Compiler Output:**\n{box(escape(compile_log, mass_mentions=True, formatting=True))}**Execution Output:**\n{box(escape(run_log, mass_mentions=True, formatting=True))}", color=0xffcc00)
await ctx.send(embed=embed)
return await message.delete()
else:
embed = discord.Embed(description=f"Compiler Output:\n{box(escape(compile_log, mass_mentions=True, formatting=True))}\nExecution Output:\n{box(escape(run_log, mass_mentions=True, formatting=True))}", color=0x00ff00)
await ctx.send(embed=embed)
return await message.delete()
if tiny_output:
output = run_log
output = '\n'.join(output.split('\n')[2:]).strip()
output = '`' + escape(output, mass_mentions=True, formatting=True) + '`'
await ctx.send(output)
return await message.delete()
else:
embed = discord.Embed(description=f"**Execution Output:**\n{box(escape(run_log, mass_mentions=True, formatting=True))}", color=0x00ff00)
await ctx.send(embed=embed)
return await message.delete()

except (httpx.NetworkError, httpx.ConnectTimeout):
embed = discord.Embed(description=f"Error connecting to listener", color=0xff0000)
Expand All @@ -163,9 +185,12 @@ async def compile(self, ctx, version:str = "latest", *,code:str):

def cleanup_code(self, content):
"""clears those pesky codeblocks"""
content = content.strip()
if content.startswith("```") and content.endswith("```"):
content = CODE_BLOCK_RE.sub("", content)[:-3]
return content.strip('\n')
elif content.startswith("`") and content.endswith("`"):
return 'world.log << json_encode(' + content[1:-1] + ')'
else:
return None

Expand Down