Skip to content

Commit

Permalink
Merge pull request #20 from madeddie/debug_flag
Browse files Browse the repository at this point in the history
Add debug flag
  • Loading branch information
madeddie committed May 12, 2024
2 parents f93d1c1 + b16aeab commit f3c0f00
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 47 deletions.
5 changes: 4 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ repos:
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: debug-statements
- id: check-merge-conflict
- id: check-json
- id: no-commit-to-branch
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
Expand Down
106 changes: 60 additions & 46 deletions moocfi_cses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# TEST: is the `click` library a useful option?
# TODO: if config doesn't exist fail and ask to run config creation
# TODO: make sure the correct directories exist
# TODO: check validity of config after creation (can we log in?)
Expand All @@ -7,6 +6,7 @@
# TODO: UI for submitting solutions
import argparse
from dataclasses import dataclass, field
from enum import Enum
from getpass import getpass
import logging
import json
Expand Down Expand Up @@ -39,7 +39,7 @@ def is_logged_in(self) -> bool:
login_text = login_link.get("text") or ""
return self.username in login_text

# TODO: add a debug flag/verbose flag and allow printing of html and forms
# TODO: create custom exceptions
def login(self) -> None:
"""Logs into the site using webscraping
Expand Down Expand Up @@ -110,32 +110,36 @@ def parse_args(args: Optional[list[str]] = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Interact with mooc.fi CSES instance")
parser.add_argument("--username", help="tmc.mooc.fi username")
parser.add_argument("--password", help="tmc.mooc.fi password")
(
parser.add_argument(
"--course",
help="SLUG of the course (default: %(default)s)",
default="dsa24k",
),
) # pyright: ignore[reportUnusedExpression]
(
parser.add_argument(
"--config",
help="Location of config file (default: %(default)s)",
default="~/.config/moocfi_cses/config.json",
),
) # pyright: ignore[reportUnusedExpression]
parser.add_argument(
"--debug", help="set logging level to debug", action="store_true"
)
parser.add_argument(
"--course",
help="SLUG of the course (default: %(default)s)",
default="dsa24k",
)
parser.add_argument(
"--config",
help="Location of config file (default: %(default)s)",
default="~/.config/moocfi_cses/config.json",
)
parser.add_argument(
"--no-state",
help="Don't store cookies or cache (they're used for faster access on the future runs)",
action="store_true",
help="Don't store cookies or cache (used for faster access on the future runs)",
)
subparsers = parser.add_subparsers(required=True)

parser_config = subparsers.add_parser("configure", help="configure moocfi_cses")
parser_config = subparsers.add_parser("configure", help="Configure moocfi_cses")
parser_config.set_defaults(cmd="configure")

parser_list = subparsers.add_parser("list", help="list exercises")
parser_list = subparsers.add_parser("list", help="List exercises")
parser_list.set_defaults(cmd="list")
parser_list.add_argument(
"--filter",
help="List complete, incomplete or all tasks (default: %(default)s)",
choices=["complete", "incomplete"],
)

return parser.parse_args(args)

Expand All @@ -153,19 +157,24 @@ def create_config() -> dict[str, str]:


# TODO: check if file exists and ask permission to overwrite
# TODO: check if path exists, otherwise create
def write_config(config_file: str, config: dict[str, str]) -> None:
def write_config(configfile: str, config: dict[str, str]) -> None:
file_path = Path(configfile).expanduser()
if file_path.exists():
# TODO: check if file exists and ask permission to overwrite
# Prompt user or handle file overwrite scenario
...
file_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
print("Writing config to file")
with open(config_file, "w") as f:
with open(file_path, "w") as f:
json.dump(config, f)


# TODO: check if path exists
# TODO: try/except around open and json.load, return empty dict on failure
def read_config(configfile: str) -> dict[str, str]:
config = dict()
file = Path(configfile).expanduser()
with open(file, "r") as f:
file_path = Path(configfile).expanduser()
with open(file_path, "r") as f:
config = json.load(f)
for setting in ("username", "password"):
assert setting in config
Expand Down Expand Up @@ -227,16 +236,25 @@ def parse_form(html: AnyStr, xpath: str = ".//form") -> dict[str, str | None]:
return form_data


class TaskState(Enum):
COMPLETE = "complete"
INCOMPLETE = "incomplete"


TASK_STATE_ICON = {
TaskState.COMPLETE: "✅",
TaskState.INCOMPLETE: "❌",
}


@dataclass
class Task:
id: str
name: str
complete: bool
state: TaskState


# NOTE: I could simply use html2text to output the list of tasks
# it needs some work to replace the <span task-score icon full with an actual icon
# and do we want people to choose the task by name or by ID (or both?)
# TODO: this should be part of a client class or module
def parse_task_list(html: str | bytes) -> list[Task]:
"""Parse html to find tasks and their status, return something useful, possibly a specific data class"""
content_element = htmlement.fromstring(html).find('.//div[@class="content"]')
Expand All @@ -260,42 +278,40 @@ def parse_task_list(html: str | bytes) -> list[Task]:
task = Task(
id=item_id,
name=item_name,
complete="full" in item_class,
state=TaskState.COMPLETE
if "full" in item_class
else TaskState.INCOMPLETE,
)
task_list.append(task)

return task_list


TASK_DONE_ICON = {True: "✅", False: "❌"}


# TODO: todo todo
def print_task_list(html: str | bytes) -> None:
"i❌ ✅ X or ✔"
print("These are you tasks")
print("these are the args")
print(html)
# TODO: This should be part of a UI class or module
def print_task_list(task_list: list[Task], filter: Optional[str] = None) -> None:
for task in task_list:
if not filter or filter == task.state.value:
print(f"- {task.id}: {task.name} {TASK_STATE_ICON[task.state]}")


# TODO: todo todo todo
# TODO: Implement function that posts the submit form with the correct file
def submit_task(task_id: str, filename: str) -> None:
"""submit file to the submit form or task_id"""
# NOTE: use parse_form
...


# TODO: todo todo todo
# TODO: Implement function that parser the specific task page into Task object
def parse_task(html: str | bytes, task: Task) -> Task:
task = Task("a", "b", True)
task = Task("a", "b", TaskState.COMPLETE)
return task


def main() -> None:
args = parse_args()

logging.basicConfig(
level=logging.WARNING,
level=logging.DEBUG if args.debug else logging.WARNING,
format="%(asctime)s %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
Expand All @@ -314,11 +330,10 @@ def main() -> None:

cookiefile = None
cookies = dict()
print(args)
if not args.no_state:
state_dir = Path("~/.local/state/moocfi_cses").expanduser()
if not state_dir.exists():
state_dir.mkdir(parents=True)
state_dir.mkdir(parents=True, exist_ok=True)
cookiefile = state_dir / "cookies.txt"
cookies = read_cookie_file(str(cookiefile))

Expand All @@ -337,8 +352,7 @@ def main() -> None:
if args.cmd == "list":
html = session.http_request(base_url)
task_list = parse_task_list(html)
for task in task_list:
print(f"- {task.id}: {task.name} {TASK_DONE_ICON[task.complete]}")
print_task_list(task_list, filter=args.filter)


if __name__ == "__main__":
Expand Down

0 comments on commit f3c0f00

Please sign in to comment.