diff --git a/.gitignore b/.gitignore index 1134622..733871e 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ run/ *.iml *.ipr *.iws +compiled/ diff --git a/README.md b/README.md index 8c93eec..d81387f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,27 @@ # CSAssignments + CS assignments from school, may or may not include assignments from other people + +# Running + +Firstly, you need to clone this repo to your machine: + +```bash +git clone https://github.com/RGBCube/CSAssignments +cd CSAssignments +``` + +OK, now you have the code on your machine. You can use the interactive code runner script to run the +code: + +Linux: + +```bash +./run +``` + +Windows: + +```bat +run.bat +``` diff --git a/run b/run new file mode 100755 index 0000000..67c8465 --- /dev/null +++ b/run @@ -0,0 +1,280 @@ +#!/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) diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..08e939f --- /dev/null +++ b/run.bat @@ -0,0 +1 @@ +py run \ No newline at end of file diff --git a/src/C/BGCOLOR b/src/C/BGCOLOR new file mode 100644 index 0000000..a66fa0d --- /dev/null +++ b/src/C/BGCOLOR @@ -0,0 +1 @@ +00599d \ No newline at end of file diff --git a/src/C/COMPILECMD b/src/C/COMPILECMD new file mode 100644 index 0000000..c3cba06 --- /dev/null +++ b/src/C/COMPILECMD @@ -0,0 +1 @@ +gcc -o {out} {main} \ No newline at end of file diff --git a/src/C/DESCRIPTION b/src/C/DESCRIPTION new file mode 100644 index 0000000..b754c14 --- /dev/null +++ b/src/C/DESCRIPTION @@ -0,0 +1 @@ +C (pronounced like the letter c) is a general-purpose computer programming language \ No newline at end of file diff --git a/src/C/FGCOLOR b/src/C/FGCOLOR new file mode 100644 index 0000000..de53aff --- /dev/null +++ b/src/C/FGCOLOR @@ -0,0 +1 @@ +ffffff \ No newline at end of file diff --git a/src/c/star-rating:30.11.2022/ASSIGNMENT.txt b/src/C/star-rating:30.11.2022/ASSIGNMENT similarity index 98% rename from src/c/star-rating:30.11.2022/ASSIGNMENT.txt rename to src/C/star-rating:30.11.2022/ASSIGNMENT index a580562..d159ff0 100644 --- a/src/c/star-rating:30.11.2022/ASSIGNMENT.txt +++ b/src/C/star-rating:30.11.2022/ASSIGNMENT @@ -20,4 +20,4 @@ number. See screenshot. contains the percentage of ratings for each star, e.g. 14.3% one star, 23.8% two star, etc. Display this to the screen in a similar manner -to task 4. +to task 4. \ No newline at end of file diff --git a/src/C/star-rating:30.11.2022/DESCRIPTION b/src/C/star-rating:30.11.2022/DESCRIPTION new file mode 100644 index 0000000..dcca539 --- /dev/null +++ b/src/C/star-rating:30.11.2022/DESCRIPTION @@ -0,0 +1 @@ +Does stuff with reviews TODO do desc \ No newline at end of file diff --git a/src/C/star-rating:30.11.2022/MAIN b/src/C/star-rating:30.11.2022/MAIN new file mode 100644 index 0000000..52efa0a --- /dev/null +++ b/src/C/star-rating:30.11.2022/MAIN @@ -0,0 +1 @@ +star_rating.c \ No newline at end of file diff --git a/src/c/star-rating:30.11.2022/solution.c b/src/C/star-rating:30.11.2022/star_rating.c similarity index 100% rename from src/c/star-rating:30.11.2022/solution.c rename to src/C/star-rating:30.11.2022/star_rating.c diff --git a/src/CPython/BGCOLOR b/src/CPython/BGCOLOR new file mode 100644 index 0000000..7761eea --- /dev/null +++ b/src/CPython/BGCOLOR @@ -0,0 +1 @@ +3670a0 \ No newline at end of file diff --git a/src/CPython/DESCRIPTION b/src/CPython/DESCRIPTION new file mode 100644 index 0000000..73ec8b8 --- /dev/null +++ b/src/CPython/DESCRIPTION @@ -0,0 +1 @@ +Python is a high-level, general-purpose programming language \ No newline at end of file diff --git a/src/CPython/FGCOLOR b/src/CPython/FGCOLOR new file mode 100644 index 0000000..8f1bb31 --- /dev/null +++ b/src/CPython/FGCOLOR @@ -0,0 +1 @@ +fed140 \ No newline at end of file diff --git a/src/CPython/RUNCMD b/src/CPython/RUNCMD new file mode 100644 index 0000000..c4a3c0b --- /dev/null +++ b/src/CPython/RUNCMD @@ -0,0 +1 @@ +python3 {main} \ No newline at end of file diff --git a/src/CPython/test:1.1.1/ASSIGNMENT b/src/CPython/test:1.1.1/ASSIGNMENT new file mode 100644 index 0000000..4bad556 --- /dev/null +++ b/src/CPython/test:1.1.1/ASSIGNMENT @@ -0,0 +1 @@ +test directions \ No newline at end of file diff --git a/src/CPython/test:1.1.1/DESCRIPTION b/src/CPython/test:1.1.1/DESCRIPTION new file mode 100644 index 0000000..1b12b17 --- /dev/null +++ b/src/CPython/test:1.1.1/DESCRIPTION @@ -0,0 +1 @@ +test desc \ No newline at end of file diff --git a/src/CPython/test:1.1.1/MAIN b/src/CPython/test:1.1.1/MAIN new file mode 100644 index 0000000..9465664 --- /dev/null +++ b/src/CPython/test:1.1.1/MAIN @@ -0,0 +1 @@ +test.py \ No newline at end of file diff --git a/src/CPython/test:1.1.1/test.py b/src/CPython/test:1.1.1/test.py new file mode 100644 index 0000000..03797e7 --- /dev/null +++ b/src/CPython/test:1.1.1/test.py @@ -0,0 +1 @@ +print("test print")