<commit_before>#!/usr/bin/env python3
"""
Run gradefast with the input of a YAML file.

Licensed under the MIT License. For more, see the LICENSE file.

Author: <NAME> <<EMAIL>>
"""
import os
import sys
import threading
import webbrowser
import time
import traceback
import subprocess

try:
    import yaml
except ImportError:
    print("")
    print("*** Couldn't find YAML package!")
    print("    Please install 'PyYAML' and try again.")
    print("")
    sys.exit(1)

from .grader import Grader
from .gradebook import GradeBook


def print_bordered_message(*msgs):
    """
    Print a message surrounded by a border.
    :param msgs: The messages to print.
    """
    longest = max((len(msg) for msg in msgs), default=0)
    msgs = ["**  %-*s  **" % (longest, msg) for msg in msgs]
    width = longest + 8
    print("\n" + "*" * width)
    for msg in msgs:
        print(msg)
    print("*" * width + "\n")


def load_yaml_data(yaml_file):
    """
    Load and check the YAML file.
    """
    yaml_data = None
    try:
        with open(yaml_file, encoding="utf-8") as f:
            yaml_data = yaml.load(f)
    except FileNotFoundError:
        raise FileNotFoundError("YAML file not found: %s" % yaml_file)

    # Make sure we have all the data
    if not (yaml_data and "grades" in yaml_data
                      and "submissions" in yaml_data
                      and "commands" in yaml_data):
        raise RuntimeError("YAML data missing required items")

    return yaml_data


def _start_gradebook(grade_structure, grade_name, hostname, port):
    """
    Create and start the GradeBook web server.
    """
    gradebook = GradeBook(grade_structure, grade_name)

    # Start the main GradeBook server
    gradebook_thread = threading.Thread(
        target=lambda: gradebook.run(hostname, port, debug=True),
        daemon=True
    )
    gradebook_thread.start()

    return gradebook


def _run_grader(yaml_data, yaml_directory, on_event):
    """
    Create and run the Grader CLI.
    """
    grader = Grader(on_event)

    for submission in yaml_data["submissions"]:
        grader.add_submissions(
            os.path.join(yaml_directory, submission["path"]),
            submission["regex"],
            "check zipfiles" in submission and submission["check zipfiles"],
            "check files" in submission and submission["check files"])
    print("")

    # A function to open a shell window
    terminal_shell = None
    # A list representing executing a command
    command_shell = None

    if "config" in yaml_data:
        # Check if they've provided a command to open a shell window
        if "terminal shell" in yaml_data["config"]:
            # Re-define the terminal shell function to use their command
            def terminal_shell(path, env):
                subprocess.Popen([
                    path if arg is None else arg
                    for arg in yaml_data["config"]["terminal shell"]
                ], cwd=path, env=env)

        # Check if they've provided a command to execute a command
        if "command shell" in yaml_data["config"]:
            command_shell = yaml_data["config"]["command shell"]

    grader.run_commands(yaml_data["commands"], yaml_directory,
                        command_shell, terminal_shell)


def run(yaml_file, hostname, port):
    """
    Start gradefast based on a YAML file and server parameters.

    :param yaml_file: The path to a YAML file detailing what we should do
    :param hostname: The hostname to run the server on
    :param port: The port to run the server on
    """
    # Try to load the YAML file
    yaml_data = load_yaml_data(yaml_file)

    # Create and start the grade book WSGI app
    gradebook = _start_gradebook(<commit_msg>Check to make sure "terminal shell" and/or "command shell" exist<commit_after>#!/usr/bin/env python3
"""
Run gradefast with the input of a YAML file.

Licensed under the MIT License. For more, see the LICENSE file.

Author: <NAME> <<EMAIL>>
"""
import os
import sys
import shutil
import threading
import webbrowser
import time
import traceback
import subprocess

try:
    import yaml
except ImportError:
    print("")
    print("*** Couldn't find YAML package!")
    print("    Please install 'PyYAML' and try again.")
    print("")
    sys.exit(1)

from .grader import Grader
from .gradebook import GradeBook


def print_bordered_message(*msgs):
    """
    Print a message surrounded by a border.
    :param msgs: The messages to print.
    """
    longest = max((len(msg) for msg in msgs), default=0)
    msgs = ["**  %-*s  **" % (longest, msg) for msg in msgs]
    width = longest + 8
    print("\n" + "*" * width)
    for msg in msgs:
        print(msg)
    print("*" * width + "\n")


def load_yaml_data(yaml_file):
    """
    Load and check the YAML file.
    """
    yaml_data = None
    try:
        with open(yaml_file, encoding="utf-8") as f:
            yaml_data = yaml.load(f)
    except FileNotFoundError:
        raise FileNotFoundError("YAML file not found: %s" % yaml_file)

    # Make sure we have all the data
    if not (yaml_data and "grades" in yaml_data
                      and "submissions" in yaml_data
                      and "commands" in yaml_data):
        raise RuntimeError("YAML data missing required items")

    return yaml_data


def _start_gradebook(grade_structure, grade_name, hostname, port):
    """
    Create and start the GradeBook web server.
    """
    gradebook = GradeBook(grade_structure, grade_name)

    # Start the main GradeBook server
    gradebook_thread = threading.Thread(
        target=lambda: gradebook.run(hostname, port, debug=True),
        daemon=True
    )
    gradebook_thread.start()

    return gradebook


def _run_grader(yaml_data, yaml_directory, on_event):
    """
    Create and run the Grader CLI.
    """
    grader = Grader(on_event)

    for submission in yaml_data["submissions"]:
        grader.add_submissions(
            os.path.join(yaml_directory, submission["path"]),
            submission["regex"],
            "check zipfiles" in submission and submission["check zipfiles"],
            "check files" in submission and submission["check files"])
    print("")

    # A function to open a shell window
    terminal_shell = None
    # A list representing executing a command
    command_shell = None

    if "config" in yaml_data:
        # Check if they've provided a command to open a shell window
        if "terminal shell" in yaml_data["config"]:
            # Make sure the terminal shell exists
            terminal_shell_args = yaml_data["config"]["terminal shell"]
            if shutil.which(terminal_shell_args[0]) is None:
                raise FileNotFoundError("Could not find \"terminal shell\" " +
                                        "from config file")

            # Re-define the terminal shell function to use their command
            def terminal_shell(path, env):
                subprocess.Popen([
                    path if arg is None else arg for arg in terminal_shell_args
                ], cwd=path, env=env)

        # Check if they've provided a command to execute a command
        if "command shell" in yaml_data["config"]:
            command_shell = yaml_data["config"]["command shell"]
            if shutil.which(command_shell[0]) is None:
                raise FileNotFoundError("Could not find \"command shell\" " +
                                        "from config file")

    grader.run_commands(yaml_data["commands"], yaml_directory,
                        command_shell, terminal_shell)


def run(yaml_file, hostname, port):
    """
    Start gradefast based on a YAML file and server parameters.

    :param yaml_file: The path to a YAML file detailing what we should do
    :param hostname: The hostname to run the server on
    :param port: The port to run the server on
    """
    # Try to load the YAML file
    yaml_data = load_yaml_data(yaml_file)

    # Create and start the grade book WSGI app
    gradebook = _start_gradebook(
{'fraction_non_alphanumeric': 0.09188380501184391, 'fraction_numerical': 0.0014960728088766987, 'mean_word_length': 2.537037037037037, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4851815', 'n_tokens_mistral': 2373, 'n_tokens_neox': 2246, 'n_words': 806}