Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Deadlines to Output #114

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gatorgrade/input/command_line_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

# pylint: disable=too-many-nested-blocks
def generate_checks(
check_data_list: List[CheckData],
check_data_list: List[CheckData], deadline
) -> List[Union[ShellCheck, GatorGraderCheck]]:
"""Generate a list of checks based on check data from the configuration file.

Expand Down Expand Up @@ -64,4 +64,4 @@ def generate_checks(
gg_args.extend(["--directory", dirname, "--file", filename])
checks.append(GatorGraderCheck(gg_args=gg_args, json_info=check_data.check))

return checks
return checks, deadline
25 changes: 19 additions & 6 deletions gatorgrade/input/in_file_path.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Generates a list of commands to be run through gatorgrader."""

import datetime
from collections import namedtuple
from pathlib import Path
from typing import Any
Expand Down Expand Up @@ -37,13 +38,25 @@ def parse_yaml_file(file_path: Path) -> List[Any]:


def reformat_yaml_data(data):
"""Reformat the raw data from a YAML file into a list of tuples."""
"""Reformat the raw data from a YAML file into a list of tuples.

Args:
data: the raw data from the YAMl file.
"""
reformatted_data = []
if len(data) == 2:
setup_commands = data.pop(0) # Removes the setup commands
run_setup(setup_commands)
add_checks_to_list(None, data[0], reformatted_data)
return reformatted_data
deadline = None
data_values = len(data)

if data_values >= 2:
for i in range(data_values - 1):
if "setup" in data[i]:
setup_commands = data[i]
run_setup(setup_commands)
elif "deadline" in data[i]:
deadline = data[i]["deadline"]

add_checks_to_list(None, data[-1], reformatted_data)
return reformatted_data, deadline


def add_checks_to_list(path, data_list, reformatted_data) -> List[CheckData]:
Expand Down
5 changes: 3 additions & 2 deletions gatorgrade/input/parse_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ def parse_config(file: Path):
# use it to generate all of the checks;
# these will be valid checks that are now
# ready for execution with this tool
parse_con = generate_checks(reformat_yaml_data(parsed_yaml_file))
return parse_con
parsed_yaml_file, deadline = reformat_yaml_data(parsed_yaml_file)
parse_con, deadline = generate_checks(parsed_yaml_file, deadline)
return parse_con, deadline
# return an empty list because of the fact that the
# parsing process did not return a list with content;
# allow the calling function to handle the empty list
Expand Down
4 changes: 2 additions & 2 deletions gatorgrade/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ def gatorgrade(
# that, by default, gatorgrade should run in checking mode
if ctx.invoked_subcommand is None:
# parse the provided configuration file
checks = parse_config(filename)
checks, deadline = parse_config(filename)
# there are valid checks and thus the
# tool should run them with run_checks
if len(checks) > 0:
checks_status = run_checks(checks, report)
checks_status = run_checks(checks, report, deadline)
# no checks were created and this means
# that, most likely, the file was not
# valid and thus the tool cannot run checks
Expand Down
82 changes: 69 additions & 13 deletions gatorgrade/output/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import os
import subprocess
from datetime import datetime
from pathlib import Path
from typing import List
from typing import Tuple
Expand Down Expand Up @@ -81,22 +82,19 @@ def _run_gg_check(check: GatorGraderCheck) -> CheckResult:


def create_report_json(
passed_count,
passed_count: int,
checkResults: List[CheckResult],
percent_passed,
percent_passed: int,
deadline_info: str,
) -> dict:
"""Take checks and put them into json format in a dictionary.

Args:
passed_count: the number of checks that passed
check_information: the basic information about checks and their params
checkResults: the list of check results that will be put in json
percent_passed: the percentage of checks that passed
deadline_info: the time until/since the given deadline, if included
"""
# create list to hold the key values for the dictionary that
# will be converted into json
overall_key_list = ["amount_correct", "percentage_score", "checks"]

checks_list = []
overall_dict = {}

Expand All @@ -109,10 +107,20 @@ def create_report_json(
results_json["diagnostic"] = checkResults[i].diagnostic
checks_list.append(results_json)

# create list to hold the key values for the dictionary that
# will be converted into json
# if there isn't a deadline
if deadline_info == "N/A":
overall_key_list = ["amount_correct", "percentage_score", "checks"]
overall_value_list = [passed_count, percent_passed, checks_list]
# if there is a deadline, include it in the key and value lists
else:
overall_key_list = ["amount_correct", "percentage_score", "deadline", "checks"]
overall_value_list = [passed_count, percent_passed, deadline_info, checks_list]

# create the dictionary for all of the check information
overall_dict = dict(
zip(overall_key_list, [passed_count, percent_passed, checks_list])
)
overall_dict = dict(zip(overall_key_list, overall_value_list))

return overall_dict


Expand All @@ -129,7 +137,14 @@ def create_markdown_report_file(json: dict) -> str:
num_checks = len(json.get("checks"))

# write the total, amt correct and percentage score to md file
markdown_contents += f"# Gatorgrade Insights\n\n**Project Name:** {Path.cwd().name}\n**Amount Correct:** {(json.get('amount_correct'))}/{num_checks} ({(json.get('percentage_score'))}%)\n"
markdown_contents += f"# Gatorgrade Insights\n\n**Project Name:** {Path.cwd().name}\n**Amount Correct:** {(json.get('amount_correct'))}/{num_checks} ({(json.get('percentage_score'))}%)"

# if there is a deadline, include it
if "deadline" in json:
markdown_contents += f"\n**Deadline:** {json.get('deadline')}\n"
# else, add newline to prepare for checks
else:
markdown_contents += "\n"

# split checks into passing and not passing
for check in json.get("checks"):
Expand Down Expand Up @@ -188,6 +203,22 @@ def create_markdown_report_file(json: dict) -> str:
return markdown_contents


def calculate_deadline_time_dif(older_time: datetime, latest_time: datetime):
"""
Input two times and return the difference of the two in days, hours, minutes, and seconds.

Args:
older_time: The larger datetime object
latest_time: The smaller datetime object
"""
time_difference = older_time - latest_time
days = time_difference.days
hours, remainder = divmod(time_difference.seconds, 3600)
minutes, seconds = divmod(remainder, 60)

return days, hours, minutes, seconds


def configure_report(report_params: Tuple[str, str, str], report_output_data: dict):
"""Put together the contents of the report depending on the inputs of the user.

Expand Down Expand Up @@ -233,7 +264,9 @@ def configure_report(report_params: Tuple[str, str, str], report_output_data: di


def run_checks(
checks: List[Union[ShellCheck, GatorGraderCheck]], report: Tuple[str, str, str]
checks: List[Union[ShellCheck, GatorGraderCheck]],
report: Tuple[str, str, str],
deadline,
) -> bool:
"""Run shell and GatorGrader checks and display whether each has passed or failed.

Expand Down Expand Up @@ -277,9 +310,32 @@ def run_checks(
else:
percent = round(passed_count / len(results) * 100)

# if a deadline is included:
deadline_difference = "N/A"
if deadline != None:
# turn the string into a datetime variable
deadline = datetime.strptime(deadline[:-1], "%m/%d/%y %H:%M:%S")
# if the deadline has passed, print out late
now = datetime.now()
if now > deadline:
days, hours, minutes, seconds = calculate_deadline_time_dif(now, deadline)
deadline_difference = f"Late by {abs(days)} days, {hours} hours, {minutes} minutes, and {seconds} seconds."
print(
f"\n-~- Your assignment is late. The deadline was {abs(days)} days, {hours} hours, {minutes} minutes, and {seconds} seconds ago. -~-"
)
# else, print out the remaining time until the assignment is due
else:
days, hours, minutes, seconds = calculate_deadline_time_dif(deadline, now)
deadline_difference = f"Due in {days * -1} days, {hours} hours, {minutes} minutes, and {seconds} seconds."
print(
f"\n-~- Your assignment is due in {days * -1} days, {hours} hours, {minutes} minutes, and {seconds} seconds. -~-"
)

# if the report is wanted, create output in line with their specifications
if all(report):
report_output_data = create_report_json(passed_count, results, percent)
report_output_data = create_report_json(
passed_count, results, percent, deadline_difference
)
configure_report(report, report_output_data)

# compute summary results and display them in the console
Expand Down
12 changes: 6 additions & 6 deletions tests/input/test_input_gg_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def test_parse_config_gg_check_in_file_context_contains_file():
# Given a configuration file with a GatorGrader check within a file context
config = Path("tests/input/yml_test_files/gatorgrade_one_gg_check_in_file.yml")
# When parse_config is run
output = parse_config(config)
output, deadline = parse_config(config)
# Then the file path should be in the GatorGrader arguments
assert "file.py" in output[0].gg_args

Expand All @@ -22,7 +22,7 @@ def test_parse_config_check_gg_matchfilefragment():
# Given a configuration file with a GatorGrader check
config = Path("tests/input/yml_test_files/gatorgrade_matchfilefragment.yml")
# When parse_config is run
output = parse_config(config)
output, deadline = parse_config(config)
# Then the description, check name, and options appear in the GatorGrader arguments
assert output[0].gg_args == [
"--description",
Expand All @@ -47,7 +47,7 @@ def test_parse_config_gg_check_no_file_context_contains_no_file():
"tests/input/yml_test_files/gatorgrade_one_gg_check_no_file_context.yml"
)
# When parse_config is run
output = parse_config(config)
output, deadline = parse_config(config)
# Then the GatorGrader arguments do not contain a file path
assert output[0].gg_args == [
"--description",
Expand All @@ -63,7 +63,7 @@ def test_parse_config_parses_both_shell_and_gg_checks():
# Given a configuration file that contains a shell check and GatorGrader check
config = Path("tests/input/yml_test_files/gatorgrader_both_checks.yml")
# When parse_config is run
output = parse_config(config)
output, deadline = parse_config(config)
# Then the output should contain a shell check and GatorGrader check
assert isinstance(output[0], GatorGraderCheck)
assert isinstance(output[1], ShellCheck)
Expand All @@ -74,7 +74,7 @@ def test_parse_config_yml_file_runs_setup_shell_checks():
# Given a configuration file without setup commands
config = Path("tests/input/yml_test_files/gatorgrade_no_shell_setup_check.yml")
# When parse_config run
output = parse_config(config)
output, deadline = parse_config(config)
# Then the output should contain the GatorGrader check
assert output[0].gg_args == [
"--description",
Expand All @@ -90,6 +90,6 @@ def test_parse_config_shell_check_contains_command():
# Given a configuration file with a shell check
config = Path("tests/input/yml_test_files/gatorgrade_one_shell_command_check.yml")
# When the parse_config is run
output = parse_config(config)
output, deadline = parse_config(config)
# Then the command should be stored in the shell check
assert output[0].command == "mdl ."
Loading