1
Fork 0
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:
RGBCube 2022-11-05 21:16:42 +03:00
parent d782a4d879
commit 58f5c52db5
9 changed files with 138 additions and 122 deletions

View file

@ -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
) )

View 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

View file

@ -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"

View 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

View file

@ -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]:

View file

@ -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"""

View file

@ -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" ]

View file

@ -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.

View file

@ -2,8 +2,8 @@ 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 {}"