mirror of
https://github.com/RGBCube/CSAssignments
synced 2025-06-21 05:22:09 +00:00
Oops
This commit is contained in:
parent
df3ebcd560
commit
57f4be66d2
20 changed files with 322 additions and 1681 deletions
1241
.editorgconfig
1241
.editorgconfig
File diff suppressed because it is too large
Load diff
9
.fleet/run.json
Normal file
9
.fleet/run.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "python",
|
||||||
|
"name": "Run Interactive Runner",
|
||||||
|
"arguments": ["-m", "interactive_runner"],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -39,3 +39,5 @@ run/
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
compiled/
|
compiled/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
|
@ -11,15 +11,15 @@ git clone https://github.com/RGBCube/CSAssignments
|
||||||
cd CSAssignments
|
cd CSAssignments
|
||||||
```
|
```
|
||||||
|
|
||||||
OK, now you have the code on your machine. You can use the interactive code runner script to run the
|
Now you have the code on your machine. You can use the interactive code runner script to run the
|
||||||
code
|
code.
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
## Linux
|
## Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./run
|
./run.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
3
interactive_runner/__init__.py
Normal file
3
interactive_runner/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from .assignment import *
|
||||||
|
from .language import *
|
||||||
|
from .sources import *
|
24
interactive_runner/__main__.py
Normal file
24
interactive_runner/__main__.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import interactive_runner as lib
|
||||||
|
|
||||||
|
s = lib.Sources()
|
||||||
|
for l in s.languages.values():
|
||||||
|
print("lname",l.name)
|
||||||
|
print("lsname",l.styled_name)
|
||||||
|
print("ldesc",l.description)
|
||||||
|
print("lbc",l._build_command)
|
||||||
|
print("ldni",l.check_dependencies_installed())
|
||||||
|
print("lisc",l.is_compiled)
|
||||||
|
print("lrcmd",l._run_command)
|
||||||
|
print()
|
||||||
|
for a in l.assignments.values():
|
||||||
|
print("alang",a.language)
|
||||||
|
print("aname",a.name)
|
||||||
|
print("agivend",a.given_date)
|
||||||
|
print("adesc",a.description)
|
||||||
|
print("adirs",a.directions)
|
||||||
|
try:
|
||||||
|
a.run()
|
||||||
|
except Exception as e:
|
||||||
|
print("errrun",e)
|
||||||
|
print()
|
||||||
|
|
85
interactive_runner/assignment.py
Normal file
85
interactive_runner/assignment.py
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
__all__ = ("Assignment",)
|
||||||
|
|
||||||
|
from functools import cached_property
|
||||||
|
from typing import TypedDict, TYPE_CHECKING
|
||||||
|
from tomllib import loads as decode_toml
|
||||||
|
from os import system as cmd
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .language import Language
|
||||||
|
|
||||||
|
AssignmentConfig = TypedDict(
|
||||||
|
"AssignmentConfig",
|
||||||
|
{
|
||||||
|
"name": str,
|
||||||
|
"given-date": str,
|
||||||
|
"main-file": str,
|
||||||
|
"description": str,
|
||||||
|
"directions": str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
class Assignment:
|
||||||
|
__config: AssignmentConfig
|
||||||
|
language: Language
|
||||||
|
name: str
|
||||||
|
given_date: str
|
||||||
|
__main_file: str
|
||||||
|
description: str
|
||||||
|
directions: str
|
||||||
|
|
||||||
|
def __init__(self, directory: Path, language: Language) -> None:
|
||||||
|
self.__directory = directory
|
||||||
|
self.language = language
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def __config(self) -> AssignmentConfig:
|
||||||
|
return decode_toml((self.__directory / "assignment.toml").read_text())
|
||||||
|
|
||||||
|
def refresh(self) -> None:
|
||||||
|
del self.__config
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self.__config["name"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def given_date(self) -> str:
|
||||||
|
return self.__config["given-date"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __main_file(self) -> Path:
|
||||||
|
return self.__directory / self.__config["main-file"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return self.__config["description"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def directions(self) -> str:
|
||||||
|
return self.__config["directions"]
|
||||||
|
|
||||||
|
def compile(self, *, quiet: bool) -> int | None:
|
||||||
|
if missing := self.language.check_dependencies_installed():
|
||||||
|
raise ValueError(f"Needed depencencies are not installed: {', '.join(missing)}")
|
||||||
|
|
||||||
|
if not self.language.is_compiled:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return cmd(self.language._build_command.format(
|
||||||
|
**{
|
||||||
|
"out-file": self.__directory / "compiled.out",
|
||||||
|
"main-file": self.__main_file.absolute(),
|
||||||
|
}
|
||||||
|
) +
|
||||||
|
(QUIET_SUFFIX if quiet else "")
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self) -> int:
|
||||||
|
if self.language.is_compiled and not (self.__directory / "compiled.out").exists():
|
||||||
|
self.compile(quiet=True)
|
||||||
|
|
||||||
|
return cmd(self.language._run_command.format((self.__directory / "compiled.out").absolute() if self.language.is_compiled else self.__main_file.absolute()))
|
||||||
|
|
10
interactive_runner/consts.py
Normal file
10
interactive_runner/consts.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
__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"
|
135
interactive_runner/language.py
Normal file
135
interactive_runner/language.py
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
__all__ = ("Language",)
|
||||||
|
|
||||||
|
from .assignment import Assignment
|
||||||
|
from .consts import OS_KEY, CHECK_COMMAND_EXISTS
|
||||||
|
from typing import TYPE_CHECKING, TypedDict
|
||||||
|
from tomllib import loads as decode_toml
|
||||||
|
from chalky import hex
|
||||||
|
from functools import cached_property
|
||||||
|
from os import system as cmd
|
||||||
|
from chalky.shortcuts.sty import bold
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
LanguageConfig = TypedDict(
|
||||||
|
"LanguageConfig",
|
||||||
|
{
|
||||||
|
"name": str,
|
||||||
|
"descripton": str,
|
||||||
|
"colors": TypedDict(
|
||||||
|
"ColorConfig",
|
||||||
|
{
|
||||||
|
"foregroud": int,
|
||||||
|
"background": int,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"build": None | TypedDict(
|
||||||
|
"BuildConfig",
|
||||||
|
{
|
||||||
|
"common": str | None,
|
||||||
|
"unix": str | None,
|
||||||
|
"windows": str | None,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"run": TypedDict(
|
||||||
|
"RunConfig",
|
||||||
|
{
|
||||||
|
"common": str | None,
|
||||||
|
"unix": str | None,
|
||||||
|
"windows": str | None,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"dependencies": None | TypedDict(
|
||||||
|
"DependencyConfig",
|
||||||
|
{
|
||||||
|
"common": list[str] | None,
|
||||||
|
"unix": list[str] | None,
|
||||||
|
"windows": list[str] | None,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
class Language:
|
||||||
|
__directory: Path
|
||||||
|
__config: LanguageConfig
|
||||||
|
name: str
|
||||||
|
styled_name: str
|
||||||
|
description: str
|
||||||
|
__dependencies: list[str] | None
|
||||||
|
_build_command: str | None
|
||||||
|
is_compiled: bool
|
||||||
|
_run_command: str
|
||||||
|
assignments: dict[str, Assignment]
|
||||||
|
|
||||||
|
def __init__(self, directory: Path) -> None:
|
||||||
|
self.__directory = directory
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def __config(self) -> LanguageConfig:
|
||||||
|
return decode_toml((self.__directory / "language.toml").read_text())
|
||||||
|
|
||||||
|
def refresh(self) -> None:
|
||||||
|
del self.__config
|
||||||
|
del self.assignments
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self.__config["name"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def styled_name(self) -> str:
|
||||||
|
colors = self.__config["colors"]
|
||||||
|
# hack
|
||||||
|
return hex(colors["foreground"]) & hex(colors["background"], background=True) | self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return self.__config["description"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def __dependencies(self) -> list[str] | None:
|
||||||
|
dependencies = self.__config.get("dependencies", {})
|
||||||
|
return dependencies.get("common", dependencies.get(OS_KEY))
|
||||||
|
|
||||||
|
def check_dependencies_installed(self) -> list[str]:
|
||||||
|
if self.__dependencies is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
not_installed = []
|
||||||
|
for dependency in self.__dependencies:
|
||||||
|
exit = cmd(CHECK_COMMAND_EXISTS.format(dependency))
|
||||||
|
|
||||||
|
if exit:
|
||||||
|
not_installed.append(dependency)
|
||||||
|
|
||||||
|
return not_installed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _build_command(self) -> str | None:
|
||||||
|
build = self.__config.get("build", {})
|
||||||
|
return build.get("common", build.get(OS_KEY))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_compiled(self) -> bool:
|
||||||
|
return bool(self._build_command)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _run_command(self) -> str:
|
||||||
|
run = self.__config["run"]
|
||||||
|
return run.get("common", run.get(OS_KEY))
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def assignments(self) -> dict[str, Assignment]:
|
||||||
|
assignments = {}
|
||||||
|
|
||||||
|
for assignment_directory in self.__directory.iterdir():
|
||||||
|
if not assignment_directory.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
assignments[assignment_directory.name] = Assignment(assignment_directory, self)
|
||||||
|
|
||||||
|
return assignments
|
33
interactive_runner/sources.py
Normal file
33
interactive_runner/sources.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
__all__ = ("Sources",)
|
||||||
|
|
||||||
|
from .language import Language
|
||||||
|
from .consts import ROOT
|
||||||
|
from functools import cached_property
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
class Sources:
|
||||||
|
__directory: Path
|
||||||
|
languages: dict[str, Language]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.__directory = ROOT / "sources"
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def languages(self) -> dict[str, Language]:
|
||||||
|
languages = {}
|
||||||
|
|
||||||
|
for language_directory in self.__directory.iterdir():
|
||||||
|
if not language_directory.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
languages[language_directory.name] = Language(language_directory)
|
||||||
|
|
||||||
|
return languages
|
||||||
|
|
||||||
|
def refresh(self) -> None:
|
||||||
|
del self.languages
|
280
run
280
run
|
@ -1,280 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from functools import cached_property
|
|
||||||
from os import name as os_name, system as cmd
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import TypeVar
|
|
||||||
|
|
||||||
from chalky import hex, sty
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
ROOT = Path(__file__).parent
|
|
||||||
is_windows = os_name == "nt"
|
|
||||||
|
|
||||||
|
|
||||||
class Sources:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.__sources_dir = (ROOT / "src")
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def languages(self) -> dict[str, Language]:
|
|
||||||
languages = {}
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for ldr in self.__sources_dir.iterdir():
|
|
||||||
if ldr.is_dir():
|
|
||||||
i += 1
|
|
||||||
language = Language(ldr)
|
|
||||||
languages[language.name] = language
|
|
||||||
languages[str(i)] = language
|
|
||||||
|
|
||||||
return languages
|
|
||||||
|
|
||||||
|
|
||||||
class Language:
|
|
||||||
def __init__(self, path: Path) -> None:
|
|
||||||
self.__path = path
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def name(self) -> str:
|
|
||||||
return self.__path.name
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def colored_name(self) -> str:
|
|
||||||
return hex((self.__path / "FGCOLOR").read_text()) & \
|
|
||||||
hex((self.__path / "BGCOLOR").read_text(), background=True) \
|
|
||||||
| self.__path.name
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def description(self) -> str:
|
|
||||||
return (self.__path / "DESCRIPTION").read_text()
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def is_compiled(self) -> bool:
|
|
||||||
return (self.__path / "COMPILECMD").exists()
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def compile_command(self) -> str:
|
|
||||||
"""For compiled languages. Format with `main` and `out`"""
|
|
||||||
return (self.__path / "COMPILECMD").read_text().strip() if self.is_compiled else None
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def run_command(self) -> str:
|
|
||||||
"""For interpreted languages. Format with `main`"""
|
|
||||||
return (self.__path / "RUNCMD").read_text().strip()
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def assignments(self) -> dict[str, Assignment]:
|
|
||||||
assignments = {}
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for adr in self.__path.iterdir():
|
|
||||||
if adr.is_dir():
|
|
||||||
i += 1
|
|
||||||
assignment = Assignment(adr, self)
|
|
||||||
assignments[assignment.name] = assignment
|
|
||||||
assignments[str(i)] = assignment
|
|
||||||
|
|
||||||
return assignments
|
|
||||||
|
|
||||||
|
|
||||||
class Assignment:
|
|
||||||
def __init__(self, path: Path, language: Language) -> None:
|
|
||||||
self.__path = path
|
|
||||||
self.language = language
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def name(self) -> str:
|
|
||||||
return self.__path.name.split(":", 1)[0]
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def date(self) -> str:
|
|
||||||
return self.__path.name.split(":", 1)[1]
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def description(self) -> str:
|
|
||||||
return (self.__path / "DESCRIPTION").read_text()
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def directions(self) -> str:
|
|
||||||
return (self.__path / "ASSIGNMENT").read_text()
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def main_file(self) -> Path:
|
|
||||||
return self.__path / (self.__path / "MAIN").read_text()
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def compiled_file(self) -> Path:
|
|
||||||
return ROOT / "compiled" / (self.name + (".exe" if is_windows else ""))
|
|
||||||
|
|
||||||
def compile(self, *, show: bool) -> bool:
|
|
||||||
"""Returns True if compilation was successful, False otherwise."""
|
|
||||||
if self.compiled_file.exists():
|
|
||||||
self.compiled_file.unlink()
|
|
||||||
return cmd(
|
|
||||||
self.language.compile_command.format(main=self.main_file, out=self.compiled_file)
|
|
||||||
+ "" if show else (" > NUL" if is_windows else " > /dev/null")
|
|
||||||
) == 0
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
if self.language.is_compiled:
|
|
||||||
if not self.compiled_file.exists():
|
|
||||||
self.compile(show=False)
|
|
||||||
cmd(self.compiled_file.absolute())
|
|
||||||
else:
|
|
||||||
cmd(self.language.run_command.format(main=self.main_file.absolute()))
|
|
||||||
|
|
||||||
|
|
||||||
src = Sources()
|
|
||||||
|
|
||||||
red = hex("ff0000")
|
|
||||||
green = hex("39ff14")
|
|
||||||
cyan = hex("4bf0fc")
|
|
||||||
purple = hex("b026ff")
|
|
||||||
orange = hex("ff760d")
|
|
||||||
|
|
||||||
invalid_choice = red | "Invalid choice! Try again."
|
|
||||||
exiting = red | "Exiting..."
|
|
||||||
|
|
||||||
|
|
||||||
def nl() -> None:
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
_nl = nl
|
|
||||||
|
|
||||||
|
|
||||||
def do_exit(s: str, /) -> str:
|
|
||||||
if s == "exit":
|
|
||||||
print(exiting)
|
|
||||||
exit()
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def nprnt(*s, nl: bool = False) -> None:
|
|
||||||
print(*s)
|
|
||||||
if nl:
|
|
||||||
_nl()
|
|
||||||
|
|
||||||
|
|
||||||
def scan(prompt: str, /, *, nl: bool = False) -> str:
|
|
||||||
s = input(prompt).lower().strip()
|
|
||||||
if nl:
|
|
||||||
_nl()
|
|
||||||
return do_exit(s)
|
|
||||||
|
|
||||||
|
|
||||||
def yesno(prompt: str, /, *, nl: bool = False) -> bool:
|
|
||||||
s = input(prompt).lower().strip()
|
|
||||||
if s in {"y", "yes"}:
|
|
||||||
return True
|
|
||||||
elif s in {"n", "no"}:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
print(invalid_choice)
|
|
||||||
return yesno(prompt, nl=nl)
|
|
||||||
|
|
||||||
|
|
||||||
def iter_int_keys(d: dict[str, T]) -> list[tuple[int, T]]:
|
|
||||||
for k, v in d.items():
|
|
||||||
if k.isdigit():
|
|
||||||
yield int(k), v
|
|
||||||
|
|
||||||
|
|
||||||
def get_ignorecase(d: dict[str, T], k: str) -> T:
|
|
||||||
for key, value in d.items():
|
|
||||||
# k is always lowercase
|
|
||||||
if key.lower() == k:
|
|
||||||
return value
|
|
||||||
raise KeyError(k)
|
|
||||||
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# TODO align columns
|
|
||||||
print("----------", green & sty.bold | "LANGUAGES", "----------")
|
|
||||||
|
|
||||||
for i, language in iter_int_keys(src.languages):
|
|
||||||
print((green | i) + f": {language.colored_name} - {language.description}")
|
|
||||||
nl()
|
|
||||||
|
|
||||||
choice = scan("Choose a language by its number or name (exit to exit): ", nl=True)
|
|
||||||
|
|
||||||
language = get_ignorecase(src.languages, choice)
|
|
||||||
|
|
||||||
if language is None:
|
|
||||||
nprnt(invalid_choice, nl=True)
|
|
||||||
continue
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# TODO align columns
|
|
||||||
print("----------", cyan & sty.bold | language.name.upper(), "----------")
|
|
||||||
|
|
||||||
for i, assignment in iter_int_keys(language.assignments):
|
|
||||||
print(
|
|
||||||
(cyan | i) + f": {assignment.name} ({assignment.date}) - {assignment.description}"
|
|
||||||
)
|
|
||||||
nl()
|
|
||||||
|
|
||||||
choice = scan(
|
|
||||||
"Choose an assignment by its number or name (back to go back, exit to exit): ",
|
|
||||||
nl=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if choice == "back":
|
|
||||||
break
|
|
||||||
|
|
||||||
assignment = get_ignorecase(language.assignments, choice)
|
|
||||||
|
|
||||||
if assignment is None:
|
|
||||||
nprnt(invalid_choice, nl=True)
|
|
||||||
continue
|
|
||||||
|
|
||||||
while True:
|
|
||||||
print("----------", purple & sty.bold | assignment.name.upper(), "----------")
|
|
||||||
print((purple | 1) + ": Show directions")
|
|
||||||
if language.is_compiled:
|
|
||||||
print((purple | 2) + ": Compile (uses cache if exists) & run")
|
|
||||||
print((purple | 3) + ": Compile manually")
|
|
||||||
else:
|
|
||||||
print((purple | 2) + ": Run")
|
|
||||||
nl()
|
|
||||||
|
|
||||||
choice = scan(
|
|
||||||
"Choose an option by its number (back to go back, exit to exit): ",
|
|
||||||
nl=True
|
|
||||||
)
|
|
||||||
|
|
||||||
match choice:
|
|
||||||
case "back":
|
|
||||||
break
|
|
||||||
|
|
||||||
case "1":
|
|
||||||
print(
|
|
||||||
"----------",
|
|
||||||
orange & sty.bold | f"DIRECTIONS FOR {assignment.name.upper()}",
|
|
||||||
"----------"
|
|
||||||
)
|
|
||||||
nprnt(assignment.directions, nl=True)
|
|
||||||
|
|
||||||
case "2":
|
|
||||||
nprnt("----------", orange | "RUN OUTPUT", "----------", nl=True)
|
|
||||||
assignment.run()
|
|
||||||
nl()
|
|
||||||
|
|
||||||
case "3":
|
|
||||||
if not language.is_compiled:
|
|
||||||
nprnt(invalid_choice, nl=True)
|
|
||||||
continue
|
|
||||||
|
|
||||||
ask = yesno("Show compile output if exists? (y/n): ", nl=True)
|
|
||||||
print(orange | "Compiling...")
|
|
||||||
success = assignment.compile(show=ask)
|
|
||||||
if success:
|
|
||||||
s = green | "Compiled successfully!"
|
|
||||||
else:
|
|
||||||
s = red | "Compilation failed!"
|
|
||||||
nprnt(s, nl=True)
|
|
||||||
|
|
||||||
case _:
|
|
||||||
nprnt(invalid_choice, nl=True)
|
|
3
run.bat
3
run.bat
|
@ -1 +1,2 @@
|
||||||
py run
|
py -m pip install -U chalky > NUL
|
||||||
|
py -m interactive_runner
|
||||||
|
|
137
run.py
137
run.py
|
@ -1,137 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from tomllib import loads as decode_toml
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from functools import cached_property
|
|
||||||
from os import name as os_name, system as cmd
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from chalky import Chalk
|
|
||||||
|
|
||||||
ROOT = Path(__file__).parent
|
|
||||||
IS_WIN = os_name == "nt"
|
|
||||||
QUIET_SUFFIX = " > NUL" if IS_WIN else " > /dev/null"
|
|
||||||
OS_KEY = "windows" if IS_WIN else "linux"
|
|
||||||
|
|
||||||
|
|
||||||
class Sources:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.__src_dir = ROOT / "src"
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def languages(self) -> dict[str, Language]:
|
|
||||||
languages = {}
|
|
||||||
|
|
||||||
for language_dir in self.__src_dir.iterdir():
|
|
||||||
if not language_dir.is_dir():
|
|
||||||
continue
|
|
||||||
|
|
||||||
language_metadata = decode_toml((language_dir / "language.toml").read_text())
|
|
||||||
language_metadata["directory"] = language_dir
|
|
||||||
|
|
||||||
language = Language.from_raw(language_metadata)
|
|
||||||
|
|
||||||
languages[language.name] = language
|
|
||||||
|
|
||||||
return languages
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Colors:
|
|
||||||
fg: Chalk
|
|
||||||
bg: Chalk
|
|
||||||
|
|
||||||
|
|
||||||
class Language:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
directory: Path,
|
|
||||||
|
|
||||||
name: str,
|
|
||||||
description: str,
|
|
||||||
colors: Colors,
|
|
||||||
build_command: str | None,
|
|
||||||
run_command: str,
|
|
||||||
) -> None:
|
|
||||||
self.__directory = directory
|
|
||||||
|
|
||||||
self.name = name
|
|
||||||
self.description = description
|
|
||||||
self.colors = colors
|
|
||||||
self.build_command = build_command
|
|
||||||
self.run_command = run_command
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_raw(cls, raw: dict) -> Language:
|
|
||||||
colors = raw["colors"]
|
|
||||||
|
|
||||||
build = raw.get("build", {})
|
|
||||||
run = raw["run"]
|
|
||||||
return cls(
|
|
||||||
directory=raw["directory"],
|
|
||||||
|
|
||||||
name=raw["name"],
|
|
||||||
description=raw["description"],
|
|
||||||
colors=Colors(
|
|
||||||
fg=colors["foreground"],
|
|
||||||
bg=colors["background"],
|
|
||||||
),
|
|
||||||
build_command=build.get("common", build.get(OS_KEY)),
|
|
||||||
run_command=run.get("common", run.get(OS_KEY)),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def assignments(self) -> dict[str, Assignment]:
|
|
||||||
for assignment_dir in self.__directory.iterdir():
|
|
||||||
if not assignment_dir.is_dir():
|
|
||||||
continue
|
|
||||||
|
|
||||||
assignment_metadata = decode_toml((assignment_dir / "assignment.toml").read_text())
|
|
||||||
assignment_metadata["directory"] = assignment_dir
|
|
||||||
assignment_metadata["language"] = self
|
|
||||||
|
|
||||||
yield Assignment.from_raw(assignment_metadata)
|
|
||||||
|
|
||||||
|
|
||||||
class Assignment:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
language: Language,
|
|
||||||
directory: Path,
|
|
||||||
|
|
||||||
name: str,
|
|
||||||
date: str,
|
|
||||||
description: str,
|
|
||||||
directions: str,
|
|
||||||
) -> None:
|
|
||||||
self.language = language
|
|
||||||
self.__directory = directory
|
|
||||||
|
|
||||||
self.name = name
|
|
||||||
self.date = date
|
|
||||||
self.description = description
|
|
||||||
self.directions = directions
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_raw(cls, raw: dict) -> Assignment:
|
|
||||||
return cls(
|
|
||||||
language=raw["language"],
|
|
||||||
directory=raw["directory"],
|
|
||||||
|
|
||||||
name=raw["name"],
|
|
||||||
date=raw["date"],
|
|
||||||
description=raw["description"],
|
|
||||||
directions=raw["directions"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def compile(self, *, quiet: bool = False) -> bool | None:
|
|
||||||
"""None = not a compiled language"""
|
|
||||||
if self.language.build_command is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return cmd(self.language.build_command + (QUIET_SUFFIX if quiet else "")) == 0
|
|
3
run.sh
Normal file
3
run.sh
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/bash
|
||||||
|
python3 -m pip install -U chalky > /dev/null
|
||||||
|
python3 -m interactive_runner
|
|
@ -1,9 +1,8 @@
|
||||||
name = "example"
|
name = "Example"
|
||||||
given-date = "3/11/2022"
|
given-date = "3/11/2022"
|
||||||
|
main-file = "example.c"
|
||||||
description = "This is an example entry for C"
|
description = "This is an example entry for C"
|
||||||
directions = """
|
directions = """
|
||||||
Print "Example" 10 times.
|
Print "Example" 10 times.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[compile]
|
|
||||||
main-file = "example.c"
|
|
||||||
|
|
|
@ -2,15 +2,14 @@ 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]
|
[colors]
|
||||||
foreground = 0x000000
|
foreground = "ffffff"
|
||||||
background = 0x00599d
|
background = "00599d"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
common = "gcc -o {out-file} {main-file}"
|
common = "gcc -o {out-file} {main-file}"
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
linux = "./{out-file}"
|
common = "{}"
|
||||||
windows = "{out-file}"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common = ["gcc"]
|
common = ["gcc"]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
name = "star-rating"
|
name = "Star Rating"
|
||||||
given-date = "30/10/2022"
|
given-date = "30/10/2022"
|
||||||
|
main-file = "star_rating.c"
|
||||||
description = "A program that takes 21 reviews and calculates the average rating and prints some stars"
|
description = "A program that takes 21 reviews and calculates the average rating and prints some stars"
|
||||||
directions = """
|
directions = """
|
||||||
1. Read in 21 user ratings for a movie.
|
1. Read in 21 user ratings for a movie.
|
||||||
|
@ -26,6 +27,3 @@ star, e.g. 14.3% one star, 23.8% two star, etc.
|
||||||
Display this to the screen in a similar manner
|
Display this to the screen in a similar manner
|
||||||
to task 4.
|
to task 4.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[compile]
|
|
||||||
main-file = "star_rating.c"
|
|
|
@ -1,9 +1,7 @@
|
||||||
name = "example"
|
name = "example"
|
||||||
given-date = "3/11/2022"
|
given-date = "3/11/2022"
|
||||||
|
main-file = "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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[compile]
|
|
||||||
main-file = "example.py"
|
|
||||||
|
|
|
@ -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 = 0xfed140
|
foreground = "fed140"
|
||||||
background = 0x3670a0
|
background = "3670a0"
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
linux = "python3 {main-file}"
|
unix = "python3 {}"
|
||||||
windows = "py {main-file}"
|
windows = "py {}"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
linux = ["python3"]
|
unix = ["python3"]
|
||||||
windows = ["py"]
|
windows = ["py"]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue