mirror of
https://github.com/RGBCube/CSAssignments
synced 2025-06-19 12:32:09 +00:00
Refactor most of the interactive runner
This commit is contained in:
parent
d782a4d879
commit
58f5c52db5
9 changed files with 138 additions and 122 deletions
|
@ -3,35 +3,33 @@ from __future__ import annotations
|
||||||
__all__ = ("Assignment",)
|
__all__ = ("Assignment",)
|
||||||
|
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from os import system as cmd
|
from .helpers import command
|
||||||
from tomllib import loads as decode_toml
|
from tomllib import loads as decode_toml
|
||||||
from typing import TYPE_CHECKING, TypedDict
|
from typing import TYPE_CHECKING, TypedDict
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
from .language import Language
|
from .language import Language
|
||||||
|
|
||||||
AssignmentConfig = TypedDict(
|
|
||||||
"AssignmentConfig",
|
class AssignmentConfig(TypedDict):
|
||||||
{
|
name: str
|
||||||
"name": str,
|
date: str
|
||||||
"given-date": str,
|
main: str
|
||||||
"main-file": str,
|
description: str
|
||||||
"description": str,
|
directions: str
|
||||||
"directions": str,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Assignment:
|
class Assignment:
|
||||||
__config: AssignmentConfig
|
__config: AssignmentConfig
|
||||||
|
__main: str
|
||||||
language: Language
|
language: Language
|
||||||
name: str
|
name: str
|
||||||
given_date: str
|
date: str
|
||||||
__main_file: str
|
|
||||||
description: str
|
description: str
|
||||||
directions: str
|
directions: str
|
||||||
|
|
||||||
def __init__(self, directory: Path, language: Language) -> None:
|
def __init__(self, directory: Path, language: Language, /) -> None:
|
||||||
self.__directory = directory
|
self.__directory = directory
|
||||||
self.language = language
|
self.language = language
|
||||||
|
|
||||||
|
@ -40,19 +38,23 @@ class Assignment:
|
||||||
return decode_toml((self.__directory / "assignment.toml").read_text())
|
return decode_toml((self.__directory / "assignment.toml").read_text())
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
del self.__config
|
"""TODO"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self.__config["name"]
|
return self.__config["name"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def given_date(self) -> str:
|
def date(self) -> str:
|
||||||
return self.__config["given-date"]
|
return self.__config["date"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __main_file(self) -> Path:
|
def __main(self) -> Path:
|
||||||
return self.__directory / self.__config["main-file"]
|
return (self.__directory / self.__config["main"]).absolute()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __out(self) -> Path:
|
||||||
|
return (self.__directory / "compiled.out").absolute()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
|
@ -63,29 +65,29 @@ class Assignment:
|
||||||
return self.__config["directions"]
|
return self.__config["directions"]
|
||||||
|
|
||||||
def compile(self, *, quiet: bool) -> int | None:
|
def compile(self, *, quiet: bool) -> int | None:
|
||||||
if missing := self.language.check_dependencies_installed():
|
if missing := self.language.check_if_dependencies_are_installed():
|
||||||
raise ValueError(f"Needed depencencies are not installed: {', '.join(missing)}")
|
raise ValueError(
|
||||||
|
f"Some dependencies that are needed are not installed: {', '.join(missing)}"
|
||||||
|
)
|
||||||
|
|
||||||
if not self.language.is_compiled:
|
if not self.language.is_compiled:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return cmd(
|
return command(
|
||||||
self.language._build_command.format(
|
self.language._build_command.format(
|
||||||
**{
|
out=self.__out,
|
||||||
"out-file": self.__directory / "compiled.out",
|
main=self.__main
|
||||||
"main-file": self.__main_file.absolute(),
|
),
|
||||||
}
|
quiet=quiet
|
||||||
) +
|
|
||||||
(QUIET_SUFFIX if quiet else "")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self) -> int:
|
def run(self) -> int:
|
||||||
if self.language.is_compiled and not (self.__directory / "compiled.out").exists():
|
if self.language.is_compiled and not (self.__directory / "compiled.out").exists():
|
||||||
self.compile(quiet=True)
|
self.compile(quiet=True)
|
||||||
|
|
||||||
return cmd(
|
return command(
|
||||||
self.language._run_command.format(
|
self.language._run_command.format(
|
||||||
(
|
self.__out if self.language.is_compiled else self.__main
|
||||||
self.__directory / "compiled.out").absolute() if self.language.is_compiled else self.__main_file.absolute()
|
),
|
||||||
)
|
quiet=False
|
||||||
)
|
)
|
||||||
|
|
8
interactive_runner/constants.py
Normal file
8
interactive_runner/constants.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
__all__ = ("ROOT", "OS")
|
||||||
|
|
||||||
|
from os import name as os_name
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
ROOT = Path(__file__).parent.parent
|
||||||
|
OS: Literal["windows"] | Literal["unix"] = "windows" if os_name == "nt" else "unix" # type: ignore
|
|
@ -1,10 +0,0 @@
|
||||||
__all__ = ("ROOT", "IS_WINDOWS", "QUIET_SUFFIX", "CHECK_COMMAND_EXISTS", "OS_KEY")
|
|
||||||
|
|
||||||
from os import name as os_name
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
ROOT = Path(__file__).parent.parent
|
|
||||||
IS_WINDOWS = os_name == "nt"
|
|
||||||
QUIET_SUFFIX = " | Out-Null" if IS_WINDOWS else " > /dev/null"
|
|
||||||
CHECK_COMMAND_EXISTS = ("Get-Command {}" if IS_WINDOWS else "command -v {}") + QUIET_SUFFIX
|
|
||||||
OS_KEY = "windows" if IS_WINDOWS else "unix"
|
|
30
interactive_runner/helpers.py
Normal file
30
interactive_runner/helpers.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
__all__ = ("command", "chalk_from_int", "command_exists")
|
||||||
|
|
||||||
|
from subprocess import DEVNULL as NULL, call as sys_command
|
||||||
|
|
||||||
|
from chalky import Chalk, TrueColor
|
||||||
|
|
||||||
|
|
||||||
|
def command(s: str, /, *, quiet: bool) -> int:
|
||||||
|
return sys_command(
|
||||||
|
s.split(" "), **(dict(stdout=NULL, stderr=NULL, stdin=NULL) if quiet else {})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def __rgb_from_int(i: int, /) -> tuple[int, int, int]:
|
||||||
|
r = (i >> 16) & 255
|
||||||
|
g = (i >> 8) & 255
|
||||||
|
b = i & 255
|
||||||
|
return r, g, b
|
||||||
|
|
||||||
|
|
||||||
|
def chalk_from_int(foreground: int, background: int = None, /) -> Chalk:
|
||||||
|
return Chalk(
|
||||||
|
foreground=TrueColor(*__rgb_from_int(foreground)),
|
||||||
|
background=TrueColor(*__rgb_from_int(background))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def command_exists(s: str, /) -> bool:
|
||||||
|
"""TODO"""
|
||||||
|
return False
|
|
@ -3,71 +3,65 @@ from __future__ import annotations
|
||||||
__all__ = ("Language",)
|
__all__ = ("Language",)
|
||||||
|
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from os import system as cmd
|
|
||||||
from tomllib import loads as decode_toml
|
from tomllib import loads as decode_toml
|
||||||
from typing import TYPE_CHECKING, TypedDict
|
from typing import TYPE_CHECKING, TypedDict
|
||||||
|
|
||||||
from chalky import hex
|
|
||||||
|
|
||||||
from .assignment import Assignment
|
from .assignment import Assignment
|
||||||
from .consts import CHECK_COMMAND_EXISTS, OS_KEY
|
from .constants import OS
|
||||||
|
from .helpers import chalk_from_int, command_exists
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
LanguageConfig = TypedDict(
|
|
||||||
"LanguageConfig",
|
class DependencyConfig(TypedDict):
|
||||||
{
|
"""The list of commands that it needs."""
|
||||||
"name": str,
|
common: list[str] | None
|
||||||
"descripton": str,
|
unix: list[str] | None
|
||||||
"colors": TypedDict(
|
windows: list[str] | None
|
||||||
"ColorConfig",
|
|
||||||
{
|
|
||||||
"foregroud": int,
|
class RunConfig(TypedDict):
|
||||||
"background": int,
|
"""command.format(main_file)"""
|
||||||
}
|
common: str | None
|
||||||
),
|
unix: str | None
|
||||||
"build": None | TypedDict(
|
windows: str | None
|
||||||
"BuildConfig",
|
|
||||||
{
|
|
||||||
"common": str | None,
|
class BuildConfig(TypedDict):
|
||||||
"unix": str | None,
|
"""command.format(out=out_file, main=main_file)"""
|
||||||
"windows": str | None,
|
common: str | None
|
||||||
}
|
unix: str | None
|
||||||
),
|
windows: str | None
|
||||||
"run": TypedDict(
|
|
||||||
"RunConfig",
|
|
||||||
{
|
class ColorConfig(TypedDict):
|
||||||
"common": str | None,
|
foreground: int
|
||||||
"unix": str | None,
|
background: int
|
||||||
"windows": str | None,
|
|
||||||
}
|
|
||||||
),
|
class LanguageConfig(TypedDict):
|
||||||
"dependencies": None | TypedDict(
|
name: str
|
||||||
"DependencyConfig",
|
description: str
|
||||||
{
|
color: ColorConfig
|
||||||
"common": list[str] | None,
|
build: BuildConfig | None
|
||||||
"unix": list[str] | None,
|
run: RunConfig
|
||||||
"windows": list[str] | None,
|
dependency: DependencyConfig | None
|
||||||
}
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Language:
|
class Language:
|
||||||
__directory: Path
|
__directory: Path
|
||||||
__config: LanguageConfig
|
__config: LanguageConfig
|
||||||
|
__dependencies: list[str] | None
|
||||||
|
_build_command: str | None
|
||||||
|
_run_command: str
|
||||||
name: str
|
name: str
|
||||||
styled_name: str
|
styled_name: str
|
||||||
description: str
|
description: str
|
||||||
__dependencies: list[str] | None
|
|
||||||
_build_command: str | None
|
|
||||||
is_compiled: bool
|
is_compiled: bool
|
||||||
_run_command: str
|
|
||||||
assignments: dict[str, Assignment]
|
assignments: dict[str, Assignment]
|
||||||
|
|
||||||
def __init__(self, directory: Path) -> None:
|
def __init__(self, directory: Path, /) -> None:
|
||||||
self.__directory = directory
|
self.__directory = directory
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
@ -75,8 +69,7 @@ class Language:
|
||||||
return decode_toml((self.__directory / "language.toml").read_text())
|
return decode_toml((self.__directory / "language.toml").read_text())
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
del self.__config
|
"""TODO"""
|
||||||
del self.assignments
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
|
@ -84,9 +77,8 @@ class Language:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def styled_name(self) -> str:
|
def styled_name(self) -> str:
|
||||||
colors = self.__config["colors"]
|
color = self.__config["color"]
|
||||||
# hack
|
return chalk_from_int(color["foreground"], color["background"]) | self.name
|
||||||
return hex(colors["foreground"]) & hex(colors["background"], background=True) | self.name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
|
@ -94,26 +86,20 @@ class Language:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __dependencies(self) -> list[str] | None:
|
def __dependencies(self) -> list[str] | None:
|
||||||
dependencies = self.__config.get("dependencies", {})
|
dependency = self.__config.get("dependency", {})
|
||||||
return dependencies.get("common", dependencies.get(OS_KEY))
|
return dependency.get("common") or dependency[OS]
|
||||||
|
|
||||||
def check_dependencies_installed(self) -> list[str]:
|
def check_if_dependencies_are_installed(self) -> list[str]:
|
||||||
|
"""Return value is the not installed dependencies"""
|
||||||
if self.__dependencies is None:
|
if self.__dependencies is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
not_installed = []
|
return [d for d in self.__dependencies if not command_exists(d)]
|
||||||
for dependency in self.__dependencies:
|
|
||||||
exit = cmd(CHECK_COMMAND_EXISTS.format(dependency))
|
|
||||||
|
|
||||||
if exit:
|
|
||||||
not_installed.append(dependency)
|
|
||||||
|
|
||||||
return not_installed
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _build_command(self) -> str | None:
|
def _build_command(self) -> str | None:
|
||||||
build = self.__config.get("build", {})
|
build = self.__config.get("build", {})
|
||||||
return build.get("common", build.get(OS_KEY))
|
return build.get("common") or build.get(OS)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_compiled(self) -> bool:
|
def is_compiled(self) -> bool:
|
||||||
|
@ -122,7 +108,7 @@ class Language:
|
||||||
@property
|
@property
|
||||||
def _run_command(self) -> str:
|
def _run_command(self) -> str:
|
||||||
run = self.__config["run"]
|
run = self.__config["run"]
|
||||||
return run.get("common", run.get(OS_KEY))
|
return run.get("common") or run[OS]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def assignments(self) -> dict[str, Assignment]:
|
def assignments(self) -> dict[str, Assignment]:
|
||||||
|
|
|
@ -5,7 +5,7 @@ __all__ = ("Sources",)
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .consts import ROOT
|
from .constants import ROOT
|
||||||
from .language import Language
|
from .language import Language
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -32,4 +32,4 @@ class Sources:
|
||||||
return languages
|
return languages
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
del self.languages
|
"""TODO"""
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
name = "C"
|
name = "C"
|
||||||
description = "C (pronounced like the letter c) is a general-purpose computer programming language"
|
description = "C (pronounced like the letter c) is a general-purpose computer programming language"
|
||||||
|
|
||||||
[colors]
|
[color]
|
||||||
foreground = "ffffff"
|
foreground = 0xffffff
|
||||||
background = "00599d"
|
background = 0x00599d
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
common = "gcc -o {out-file} {main-file}"
|
common = "gcc -o {out} {main}"
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
common = "{}"
|
common = "{}"
|
||||||
|
|
||||||
[dependencies]
|
[dependency]
|
||||||
common = ["gcc"]
|
common = [ "gcc" ]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name = "example"
|
name = "example"
|
||||||
given-date = "3/11/2022"
|
date = "3/11/2022"
|
||||||
main-file = "example.py"
|
main = "example.py"
|
||||||
description = "This is an example entry for CPython"
|
description = "This is an example entry for CPython"
|
||||||
directions = """
|
directions = """
|
||||||
Print "Example" 10 times.
|
Print "Example" 10 times.
|
||||||
|
|
|
@ -2,13 +2,13 @@ name = "CPython"
|
||||||
description = "Python is a high-level, general-purpose programming language"
|
description = "Python is a high-level, general-purpose programming language"
|
||||||
|
|
||||||
[colors]
|
[colors]
|
||||||
foreground = "fed140"
|
foreground = 0xfed140
|
||||||
background = "3670a0"
|
background = 0x3670a0
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
unix = "python3 {}"
|
unix = "python3 {}"
|
||||||
windows = "py {}"
|
windows = "py {}"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
unix = ["python3"]
|
unix = [ "python3" ]
|
||||||
windows = ["py"]
|
windows = [ "py" ]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue