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

Adds support for build configs and drops support for compiler servers older than 0.3.1 #4

Merged
merged 8 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
repos:
- repo: https://github.com/psf/black
rev: e877371
rev: e87737140f32d3cd7c44ede75f02dcd58e55820e # frozen: 23.9.1
hooks:
- id: black

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: f71fa2c
rev: c4a0b883114b00d8d76b479c820ce7950211c99b # frozen: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand All @@ -15,18 +15,18 @@ repos:
language_version: python3

- repo: https://github.com/PyCQA/flake8
rev: 7ef0350
rev: 7ef0350a439c93166bc8ba89fcc3de6a9a664e6c # frozen: 6.1.0
hooks:
- id: flake8
language_version: python3

- repo: https://github.com/asottile/reorder-python-imports
rev: ba4e0d3
rev: ae11ccbd838e90f7e2a16abbed9276e6a41cafbb # frozen: v3.12.0
hooks:
- id: reorder-python-imports

- repo: https://github.com/codespell-project/codespell
rev: 355e50e
rev: 6e41aba91fb32e9feb741a6258eefeb9c6e4a482 # frozen: v2.2.6
hooks:
- id: codespell
additional_dependencies:
Expand Down
2 changes: 2 additions & 0 deletions odcompile/_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class BadVersion(Exception):
"""Raises for unsupported server versions"""
2 changes: 2 additions & 0 deletions odcompile/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__version__ = "0.2.0"
__author__ = "Crossedfall"
54 changes: 46 additions & 8 deletions odcompile/odcompile.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from asyncio import get_event_loop

from redbot.core import checks
from redbot.core import commands
from redbot.core.config import Config

from odcompile.utils.logger import log
from odcompile.utils.misc import cleanupCode
from odcompile.utils.misc import splitArgs
from odcompile.utils.misc import versionCheck
from odcompile.utils.regex import INCLUDE_PATTERN
from odcompile.utils.relay import processCode

__version__ = "0.1.0"
__author__ = "Crossedfall"


class ODCompile(commands.Cog):
"""A discord compiler for OpenDream"""
Expand All @@ -18,9 +19,11 @@ def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 32175847454, force_registration=True)

default_config = {"listener_url": "http://localhost:5000/compile"}
default_config = {"listener_url": "http://localhost:5000", "config_version": None}

self.config.register_global(**default_config)
self.loop = get_event_loop()
self.loop.create_task(versionCheck(self))

@commands.group()
@checks.is_owner()
Expand All @@ -33,12 +36,13 @@ async def odcompileset(self, ctx):
@odcompileset.command()
async def listener(self, ctx, url: str = None):
"""
Set the full URL for the listener
Set the base URL for the listener, DO NOT include `/compile`

Should be similar to: http://localhost:5000/compile
Should be similar to: http://localhost:5000
"""

try:
url = url.rstrip("/")
await self.config.listener_url.set(url)
await ctx.send(f"Listener URL set to: {url}")
except (ValueError, KeyError, AttributeError):
Expand All @@ -52,7 +56,7 @@ async def odcompile(self, ctx, *, input: str):
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 OpenDream.

Short one-liners can be provided in basic code-markdown, for example:
`world.log < "Hello, World!"`
`world.log << "Hello, World!"`

Multi-line or explicit code must be contained within a codeblock, for example:
```c
Expand All @@ -72,6 +76,8 @@ async def odcompile(self, ctx, *, input: str):

Adding `--no-parsing` before the codeblock will provide the full execution output instead of a parsed version.

If you'd like to compile using the Debug build of OpenDream use `[p]odcompiledebug`

__Code will always be compiled with the latest version of OpenDream__
""" # noqa: E501
cleaned_input = splitArgs(args=input)
Expand All @@ -83,10 +89,42 @@ async def odcompile(self, ctx, *, input: str):
return await ctx.send("You can't have any `#include` statements in your code.")

message = await ctx.send("Compiling. If there have been any updates, this could take a moment....")
log.debug(f"Sending code to the listener to be compiled:\n{code}")

async with ctx.typing():
embed = await processCode(
self=self,
code=code,
args=cleaned_input["args"],
build_config="Release",
parsed_output=cleaned_input["parsed"],
)
await ctx.send(embed=embed)
return await message.delete()

@commands.command()
async def odcompiledebug(self, ctx, *, input: str):
"""
Compile and run DM code with OpenDream's Debug build configuration
"""
cleaned_input = splitArgs(args=input)

code = cleanupCode(cleaned_input["code"])
if code is None:
return await ctx.send("Your code has to be in a code block!")
if INCLUDE_PATTERN.search(code) is not None:
return await ctx.send("You can't have any `#include` statements in your code.")

message = await ctx.send("Compiling. If there have been any updates, this could take a moment....")
log.debug(f"Sending code to the listener to be compiled:\n{code}")

async with ctx.typing():
embed = await processCode(
self=self, code=code, args=cleaned_input["args"], parsed_output=cleaned_input["parsed"]
self=self,
code=code,
args=cleaned_input["args"],
build_config="Debug",
parsed_output=cleaned_input["parsed"],
)
await ctx.send(embed=embed)
return await message.delete()
3 changes: 3 additions & 0 deletions odcompile/utils/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import logging

log = logging.getLogger("red.cog.ODCompile")
26 changes: 26 additions & 0 deletions odcompile/utils/misc.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import json

from discord import Embed
from packaging.version import parse as parse_version
from redbot.core.utils.chat_formatting import box
from redbot.core.utils.chat_formatting import escape

from odcompile._version import __version__
from odcompile.utils.logger import log
from odcompile.utils.regex import CODE_BLOCK_RE
from odcompile.utils.regex import COMPILER_ERROR_RE
from odcompile.utils.regex import COMPILER_WARNING_RE
Expand Down Expand Up @@ -107,3 +110,26 @@ def parseRunOutput(logs: str, parsed_output: bool) -> str:
logs = SERVER_STARTING_OUTPUT_RE.sub("", logs)
logs = SERVER_ENDING_OUTPUT_RE.sub("", logs)
return logs


async def versionCheck(self) -> None:
"""
Checks the current config version and triggers actions or sends the owner a notice if needed
"""
config_version = await self.config.config_version() or "0"

if config_version == __version__:
return

await self.config.config_version.set(__version__)
log.info(f"Config version updated from {config_version} to {__version__} ")

if parse_version(config_version) < parse_version("0.2.0"):
"""
In version 0.2.0 we added version checking which requires
the base URL to be stored instead of the strict /compile URL.
"""
listener_url = await self.config.listener_url()
corrected_url = listener_url.rstrip("/compile")
await self.config.listener_url.set(corrected_url)
log.warning(f"Listener url config entry updated to '{corrected_url}' from '{listener_url}'")
46 changes: 40 additions & 6 deletions odcompile/utils/relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,49 @@
from discord import Embed
from httpx import AsyncClient
from httpx import ConnectTimeout
from httpx import HTTPStatusError
from httpx import NetworkError
from httpx import ReadTimeout
from packaging.version import parse as parse_version

from odcompile._exceptions import BadVersion
from odcompile.utils.logger import log
from odcompile.utils.misc import getEmbed


async def sendCode(listenerurl: str, code: str, args: list, timeout: int = 900) -> json:
async def sendCode(listenerurl: str, code: str, args: list, build_config: str, timeout: int = 900) -> json:
"""
Communicate with the sandbox server and return the json output
"""
async with AsyncClient() as client:
r = await client.post(listenerurl, json={"code_to_compile": code, "extra_arguments": args}, timeout=timeout)
r = await client.post(
url=f"{listenerurl}/compile",
json={"code_to_compile": code, "extra_arguments": args, "build_config": build_config},
timeout=timeout,
)
return r.json()


async def processCode(self, code: str, args: list, parsed_output: bool = True) -> Embed:
async def processCode(self, code: str, args: list, build_config: str, parsed_output: bool = True) -> Embed:
listener_url = await self.config.listener_url()
try:
r = await sendCode(listenerurl=await self.config.listener_url(), code=code, args=args)
listener_version = await getVersion(listenerurl=listener_url)
checkCompatibility(listener_version)
r = await sendCode(listenerurl=listener_url, code=code, args=args, build_config=build_config)

except (JSONDecodeError, ReadTimeout, AttributeError):
except (JSONDecodeError, ReadTimeout, AttributeError) as e:
embed = Embed(
description="There was a problem with the listener. Unable to retrieve any results!",
color=0xFF0000,
)
log.error(e)
return embed
except (NetworkError, ConnectTimeout):
except (NetworkError, ConnectTimeout) as e:
embed = Embed(description="Error connecting to listener", color=0xFF0000)
log.error(e)
return embed
except BadVersion as e:
embed = Embed(title="Compiler server out of date", description=e, color=0xFF0000)
return embed

if "build_error" in r.keys():
Expand All @@ -41,3 +57,21 @@ async def processCode(self, code: str, args: list, parsed_output: bool = True) -
return embed

return getEmbed(logs=r, parsed_output=parsed_output)


def checkCompatibility(version: str) -> bool:
if parse_version(version=version) < parse_version("0.3.1"):
raise BadVersion(f"Version {version} does not support build configs and needs to be updated.")


async def getVersion(listenerurl: str) -> str:
try:
async with AsyncClient() as client:
r = await client.get(url=f"{listenerurl}/version")
r.raise_for_status()
return r.json()["version"]
except HTTPStatusError as e:
if "404 NOT FOUND" in str(e):
raise BadVersion("The compiler server is too old and needs to be updated! Unable to fetch version number.")
else:
raise