Skip to content

Commit

Permalink
Merge pull request #102 from GatorEducator/fix/handle-bad-file
Browse files Browse the repository at this point in the history
Fix/handle bad file
  • Loading branch information
gkapfham authored Sep 7, 2022
2 parents 9d0369b + 3c1edd2 commit 87f4bfb
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 29 deletions.
24 changes: 20 additions & 4 deletions gatorgrade/input/in_file_path.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Generates a list of commands to be run through gatorgrader."""

from collections import namedtuple
from pathlib import Path
from typing import Any
from typing import List

import yaml
Expand All @@ -12,12 +14,26 @@
# which is a file path associated with the check to be used when running the check.
CheckData = namedtuple("CheckData", ["file_context", "check"])

# define the default encoding
DEFAULT_ENCODING = "utf8"

def parse_yaml_file(file_path):

def parse_yaml_file(file_path: Path) -> List[Any]:
"""Parse a YAML file and return its contents as a list of dictionaries."""
with open(file_path, encoding="utf8") as file:
data = yaml.load_all(file, Loader=yaml.FullLoader)
return list(data)
# confirm that the file exists before attempting to read from it
if file_path.exists():
# read the contents of the specified file using the default
# encoding and then parse that file using the yaml package
with open(file_path, encoding=DEFAULT_ENCODING) as file:
# after parsing with the yaml module, return a list
# of all of the contents specified in the file
data = yaml.load_all(file, Loader=yaml.FullLoader)
return list(data)
# some aspect of the file does not exist
# (i.e., wrong file or wrong directory)
# and thus parsing with YAML is not possible;
# return a blank list that calling function handles
return []


def reformat_yaml_data(data):
Expand Down
23 changes: 18 additions & 5 deletions gatorgrade/input/parse_config.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
"""Returns the list of commands to be run through gatorgrader."""

from pathlib import Path

from gatorgrade.input.command_line_generator import generate_checks
from gatorgrade.input.in_file_path import parse_yaml_file
from gatorgrade.input.in_file_path import reformat_yaml_data


def parse_config(file):
def parse_config(file: Path):
"""Parse the input yaml file and generate specified checks.
Args:
file: Yaml file containing gatorgrade and shell command checks
Returns:
Returns a dictionary that specifies shell commands and gatorgrade commands
"""
parse_con = generate_checks(
reformat_yaml_data(parse_yaml_file(file))
) # Call previously generated function to modify file
return parse_con
# parse the YAML file using parse_yaml_file provided by gatorgrade
parsed_yaml_file = parse_yaml_file(file)
# the parsed YAML file contains some contents in a list and thus
# the tool should generate a GatorGrader check for each element in list
if len(parsed_yaml_file) > 0:
# after reformatting the parse YAML file,
# 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
# 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
return []
28 changes: 26 additions & 2 deletions gatorgrade/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
from typing import List

import typer
from rich.console import Console

from gatorgrade.generate.generate import generate_config
from gatorgrade.input.parse_config import parse_config
from gatorgrade.output.output import run_checks

# create an app for the Typer-based CLI
app = typer.Typer(add_completion=False)

# create a default console for printing with rich
console = Console()

# define constants used in this module
FILE = "gatorgrade.yml"
FAILURE = 1

Expand All @@ -23,10 +29,28 @@ def gatorgrade(
filename: Path = typer.Option(FILE, "--config", "-c", help="Name of the yml file."),
):
"""Run the GatorGrader checks in the gatorgrade.yml file."""
# check if ctx.subcommand is none
# if ctx.subcommand is None then this means
# 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_status = run_checks(checks)
# there are valid checks and thus the
# tool should run them with run_checks
if len(checks) > 0:
checks_status = run_checks(checks)
# no checks were created and this means
# that, most likely, the file was not
# valid and thus the tool cannot run checks
else:
checks_status = False
console.print()
console.print(f"The file {filename} either does not exist or is not valid.")
console.print("Exiting now!")
console.print()
# at least one of the checks did not pass or
# the provided file was not valid and thus
# the tool should return a non-zero exit
# code to designate some type of failure
if checks_status is not True:
sys.exit(FAILURE)

Expand Down
32 changes: 22 additions & 10 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[tool.poetry]
name = "gatorgrade"
version = "0.2.1"
version = "0.2.2"
description = "Python tool to execute GatorGrader"
authors = ["Michael Abraham", "Jacob Allebach", "Liam Black", "Katherine Burgess", "Yanqiao Chen", "Ochirsaikhan Davaajambal", "Tuguldurnemekh Gantulga", "Anthony Grant-Cook", "Dylan Holland", "Gregory M. Kapfhammer", "Peyton Kelly", "Luke Lacaria", "Lauren Nevill", "Jack Turner", "Daniel Ullrich", "Garrison Vanzin", "Rian Watson"]

[tool.poetry.dependencies]
python = ">=3.7,<4.0"
typer = "^0.4.1"
PyYAML = "^6.0"
gatorgrader = "^1.1.0"
rich = "^12.5.1"
typer = {extras = ["all"], version = "^0.6.1"}

[tool.poetry.dev-dependencies]
taskipy = "^1.10.1"
Expand Down
16 changes: 10 additions & 6 deletions tests/input/test_input_gg_checks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test suite for parse_config function."""

from pathlib import Path

from gatorgrade.input.checks import GatorGraderCheck
from gatorgrade.input.checks import ShellCheck
from gatorgrade.input.parse_config import parse_config
Expand All @@ -8,7 +10,7 @@
def test_parse_config_gg_check_in_file_context_contains_file():
"""Test to make sure that the file context is included in the GatorGrader arguments."""
# Given a configuration file with a GatorGrader check within a file context
config = "tests/input/yml_test_files/gatorgrade_one_gg_check_in_file.yml"
config = Path("tests/input/yml_test_files/gatorgrade_one_gg_check_in_file.yml")
# When parse_config is run
output = parse_config(config)
# Then the file path should be in the GatorGrader arguments
Expand All @@ -18,7 +20,7 @@ def test_parse_config_gg_check_in_file_context_contains_file():
def test_parse_config_check_gg_matchfilefragment():
"""Test to make sure the description, check name, and options appear in the GatorGrader arguments."""
# Given a configuration file with a GatorGrader check
config = "tests/input/yml_test_files/gatorgrade_matchfilefragment.yml"
config = Path("tests/input/yml_test_files/gatorgrade_matchfilefragment.yml")
# When parse_config is run
output = parse_config(config)
# Then the description, check name, and options appear in the GatorGrader arguments
Expand All @@ -41,7 +43,9 @@ def test_parse_config_check_gg_matchfilefragment():
def test_parse_config_gg_check_no_file_context_contains_no_file():
"""Test to make sure checks without a file context do not have a file path in GatorGrader arguments."""
# Given a configuration file with a GatorGrader check without a file context
config = "tests/input/yml_test_files/gatorgrade_one_gg_check_no_file_context.yml"
config = Path(
"tests/input/yml_test_files/gatorgrade_one_gg_check_no_file_context.yml"
)
# When parse_config is run
output = parse_config(config)
# Then the GatorGrader arguments do not contain a file path
Expand All @@ -57,7 +61,7 @@ def test_parse_config_gg_check_no_file_context_contains_no_file():
def test_parse_config_parses_both_shell_and_gg_checks():
"""Test to make sure that both shell and GatorGrader checks are parsed."""
# Given a configuration file that contains a shell check and GatorGrader check
config = "tests/input/yml_test_files/gatorgrader_both_checks.yml"
config = Path("tests/input/yml_test_files/gatorgrader_both_checks.yml")
# When parse_config is run
output = parse_config(config)
# Then the output should contain a shell check and GatorGrader check
Expand All @@ -68,7 +72,7 @@ def test_parse_config_parses_both_shell_and_gg_checks():
def test_parse_config_yml_file_runs_setup_shell_checks():
"""Test to make sure that a configuration file without setup commands can be parsed."""
# Given a configuration file without setup commands
config = "tests/input/yml_test_files/gatorgrade_no_shell_setup_check.yml"
config = Path("tests/input/yml_test_files/gatorgrade_no_shell_setup_check.yml")
# When parse_config run
output = parse_config(config)
# Then the output should contain the GatorGrader check
Expand All @@ -84,7 +88,7 @@ def test_parse_config_yml_file_runs_setup_shell_checks():
def test_parse_config_shell_check_contains_command():
"""Test to make sure that the command for a shell check is stored."""
# Given a configuration file with a shell check
config = "tests/input/yml_test_files/gatorgrade_one_shell_command_check.yml"
config = Path("tests/input/yml_test_files/gatorgrade_one_shell_command_check.yml")
# When the parse_config is run
output = parse_config(config)
# Then the command should be stored in the shell check
Expand Down

0 comments on commit 87f4bfb

Please sign in to comment.