Skip to content

Commit

Permalink
Merge pull request #22 from cmpsc-481-s22-m1/release/0.1.0
Browse files Browse the repository at this point in the history
Release/0.1.0
  • Loading branch information
ColemanKobe authored Jan 28, 2022
2 parents 343ff0e + b8fdd49 commit 4369217
Show file tree
Hide file tree
Showing 25 changed files with 1,413 additions and 114 deletions.
17 changes: 17 additions & 0 deletions .github/ISSUE_TEMPLATE/new-feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: New Feature
about: Task for team members to complete
title: ''
labels: ''
assignees: ''

---

**What is the task**
A clear and concise description of what the task is

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Additional context**
Add any other context or screenshots about the feature request here.
10 changes: 9 additions & 1 deletion .github/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@
"virtualenvs",
"git",
"gitignore",
"github"
"github",
"favou",
"pwsh",
"fastfail",
"gatorconfig",
"gatorgrader"



],
"ignorePaths": [
".github/**",
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,9 @@ jobs:
poetry env info
- name: Install dependencies
run: poetry install --no-interaction --no-ansi
- name: setup ${{ matrix.os }}
run: |
sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX
- name: Execute tests
run: poetry run task test
7 changes: 7 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[MASTER]

# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=PyQt5
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,59 @@
# project-team-4

# team4-GatorConfig

A simple Python project utilizing a CLI approach to automate generating
configuration files for GatorGrader. The GitHub Actions workflow executes
[pytest](https://pytest.org/) (with
[coverage](https://pypi.org/project/pytest-cov/)) and
[pylint](https://pylint.org/) using the Poetry configuration, and checks
markdown with [markdownlint](https://github.com/DavidAnson/markdownlint) and
spelling with [cspell](https://cspell.org/).

## Requirements

- [Python](https://realpython.com/installing-python/)
- [Pipx](https://pypa.github.io/pipx/installation/)
- [Poetry](https://python-poetry.org/docs/#installing-with-pipx)

## Usage

### Installing Python dependencies

After cloning this project, you will likely want to instruct Poetry to create a
virtual environment and install the Python packages (like pytest and pylint)
listed in `pyproject.toml`.

To install Python dependencies:

```bash
poetry install
```

### Running tasks

This project uses the [taskipy](https://github.com/illBeRoy/taskipy) task runner
to simplify testing and linting. You can see the actual commands run when tasks
are executed under the `[tool.taskipy.tasks]` header in `pyproject.toml`.

- **Test** your code with `poetry run task test`
- **Lint** your code with `poetry run task lint`

### Running GatorConfig

GatorConfig is a tool that will utilize the command line interface, which
was built to accommodate the users. To run the GatorConfig program
in CLI, type the command:

`poetry run gatorconfig`

Once you run this command, the program will output

`Wrote file to: C:\Users\<YOUR PATH>\GatorConfig\gatorgrader.yml`

This command will auto-generate a default configuration file for GatorGradle
named `gatorgrader.yml` which will contain a default input for
the variables, such as the name, break, fastfail, etc.,

Additionally, you can run the `poetry run gatorconfig --help` for more
information about the configuration. This command will list out the variables
in the file as well as the defaults it outputs.
File renamed without changes.
14 changes: 14 additions & 0 deletions gatorconfig/actions_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""This module generates a GitHub Actions configuration file that uses GatorGradle"""
from pathlib import Path
from ruamel.yaml import YAML

def create_configuration_file(target_file):
"""Create the workflow configuration file itself"""
default = {'name': 'Grade', 'on': ['push', 'pull_request'], 'jobs':
{'grade': {'runs-on': 'ubuntu-latest', 'steps':
[{'name': 'Checkout repository', 'uses': 'actions/checkout@v2'},
{'name': 'Run GatorGradle', 'uses': 'GatorEducator/gatorgradle-action@v1'}]}}}
yaml = YAML()
yaml.default_flow_style = False
out = Path(target_file)
yaml.dump(default, out)
92 changes: 92 additions & 0 deletions gatorconfig/gator_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Capture user input to automatically generate YAML file."""
from typing import Dict
from typing import List
from pathlib import Path
import typer
from gatorconfig import gator_yaml
from gatorconfig import actions_configuration

cli = typer.Typer()



#pylint: disable=too-many-arguments
@cli.command()
def cli_input(
name: str = typer.Option("Project"),
brk: bool = typer.Option(False, "--break"),
fastfail: bool = typer.Option(False),
gen_readme: bool = typer.Option(False),
file: List[str] = typer.Option([]),
#language: str = typer.Option(None),
output_path: Path = typer.Option(Path.cwd()),
indent: int = typer.Option(4),
commit_count: int = typer.Option(5)
):
"""Gather input from the command line.
Args:
name (str, optional): [description]. Defaults to typer.Option("Project").
brk (bool, optional): [description]. Defaults to typer.Option(False, "--break").
fastfail (bool, optional): [description]. Defaults to typer.Option(False).
file (List[str], optional): [description]. Defaults to typer.Option([]).
indent (int, optional): [description]. Defaults to typer.Option(4).
commit_count (int, optional): [description]. Defaults to typer.Option(5).
"""
files = get_checks(file)

yaml_out = gator_yaml.GatorYaml()
#print(files)
# Creation of the output variable
output = {
"name": name,
"break": brk,
"fastfail": fastfail,
"readme": gen_readme,
"indent": indent,
"commits": commit_count,
"files": files
}
file_yaml = yaml_out.dump(output, paths=output["files"])
output_file(file_yaml, output_path)
actions_configuration.create_configuration_file('../.github/workflows/grade.yml')


def output_file(yaml_string: str, output_path: Path):
"""Create and write to file if it doesn't exist, writes to file otherwise.
Args:
yaml_string (str): [description]
output_path (Path): [description]
"""
fle = Path(output_path / "gatorgrader.yml")
fle.touch(exist_ok=True)
with open(fle, "w", encoding="utf8") as yml:
yml.write(yaml_string)
print(f"Wrote file to: {fle}")

def get_checks(file: List[Path]) -> Dict:
"""Read in checks per file.
Args:
file (List[Path]): List of file paths read in from command line.
Returns:
Dict: Dictionary of file paths and checks to perform per file.
"""
files = {}
for item in file:
running = True
print("")
check_list = []
while running:
check = input(f"Enter a check for {item} (Press \"Enter\" to move on): ")
if check.lower() == "":
running = False
else:
check_list.append(check)
files[item] = check_list
return files

if __name__ == "__main__":
cli()
106 changes: 106 additions & 0 deletions gatorconfig/gator_yaml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Converts dictionaries to GatorYAML"""
from gatorconfig.split_file_path import split_file_path


class GatorYaml:
"""Main GatorYaml object"""

def __init__(self, indent=4, spaces=4):
"""Init GatorYAML object. Takes optional arguments to change indent of files
and how many spaces is considered a tab """
self.spaces = spaces # How many spaces is a tab
self.tabs = -1 # Current tab level
self.output = "" # Init output
self.keywords = ["(pure)", "commits"] # Any keywords to look for
self.indents = indent # set indent for file path

def dump(self, dic, paths=None):
"""Input dictionary is parsed. returns string of valid YAML"""

if paths is not None:
if isinstance(paths, dict):
dic["files"] = split_file_path(paths)
else:
raise Exception("Paths expected to be \"dict\", got " + str(type(paths)) + "!")

self.enum_dict(dic)

return self.output

def enum_list(self, list_in):
"""Enumerate through input list and output each item unless it finds another dictionary."""
for i in list_in:
if isinstance(i, dict):
self.tabs += 1
self.enum_dict(i)
else:
self.output_list_item(i)

def enum_dict(self, dic):
"""Enumerate through input dictionary and output each key"""
self.tabs += 1

for k in dic.keys():
if k == "indent":
self.indents = int(dic[k])

if k == "files":
self.tabs -= 1
# if isinstance(d[k], list):
# self.enum_list(d[k])
if isinstance(dic[k], dict):
self.enum_file_dict(dic[k])
elif isinstance(dic[k], list):
self.output_key(k)
self.enum_list(dic[k])
elif isinstance(dic[k], dict):
self.output_key(k)
self.enum_dict(dic[k])
else:
if not self.is_keyword(k, dic[k]):
self.output_key_value(k, dic[k])

self.tabs -= 1

def enum_file_dict(self, files):
"""Enumerate through the file list dictionary and output each key"""
self.tabs += 1

for k in files:
if isinstance(files[k], dict):
self.output_key(k)
self.enum_file_dict(files[k])
elif isinstance(files[k], list):
self.output_key(k)
self.enum_file_list(files[k])

self.tabs -= 1

def enum_file_list(self, list_in):
"""Enumerate through each file key's parameter list items"""
for item in list_in:
self.output += (" " * self.spaces) * self.indents + str(item) + "\n"

def output_list_item(self, item):
"""Output a generic list item"""
self.output += (" " * self.spaces) * self.tabs + " -" + str(item) + "\n"

def output_key(self, key):
"""Output a generic key"""
self.output += (" " * self.spaces) * self.tabs + str(key) + ":\n"

def output_key_value(self, key, value):
"""Output a generic key and it's value"""
self.output += ((" " * self.spaces) * self.tabs) + str(key) + ": " + str(value) + "\n"

def is_keyword(self, key, value):
"""Output key and value if a keyword"""
if key in self.keywords:
if key == "commits":
self.output += (" " * self.spaces) * self.tabs \
+ "--" + str(key) + " " + str(value) + "\n"
else:
self.output += (" " * self.spaces) * self.tabs + str(key) \
+ " " + str(value) + "\n"
return True
return False
59 changes: 59 additions & 0 deletions gatorconfig/gui/check_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Checked File widgets"""

import os
from PyQt5.QtWidgets import QWidget, \
QVBoxLayout, QLabel, \
QHBoxLayout, QLineEdit, QPushButton, \
QPlainTextEdit, QFrame, \
QFileDialog


class CheckFile(QWidget):
"""QWidget class for a checked file"""

def __init__(self, parent=None):
"""Init QWidget parent and the widgets that make up a checked file widget"""
super().__init__(parent)
self.outer_lay = QVBoxLayout()

self.file = None

# ----File Browser----#
# Hbox Layout
lay = QHBoxLayout()
self.check_label = QLabel("File ")
self.path_text = QLineEdit()
self.path_button = QPushButton("Open",
clicked=lambda: self.get_path_from_file(self.path_text))
self.file_params = QPlainTextEdit()

# Add widgets to hbox layout
lay.addWidget(self.check_label)
lay.addWidget(self.path_text)
lay.addWidget(self.path_button)

self.outer_lay.addLayout(lay)
self.outer_lay.addWidget(self.file_params)

# Horizontal line separator
self.frame = QFrame()
self.frame.setFrameShape(QFrame.HLine)
self.frame.setFrameShadow(QFrame.Sunken)

self.outer_lay.addWidget(self.frame)

self.setLayout(self.outer_lay)

def get_path_from_file(self, target_text):
"""Opens a file dialog to select a new file.
Sets self.file to the file name and sets the tab text."""
self.file = QFileDialog.getOpenFileName(self, 'OpenFile')[0]
target_text.setText(self.file)
tabs = self.parentWidget().parentWidget()
tabs.setTabText(tabs.currentIndex(), os.path.basename(self.file))

def get_data(self):
"""Gets all the data of the checked files and returns their file paths and params."""
if self.file is None:
self.file = self.path_text.text()
return self.file, self.file_params.toPlainText().split()
Loading

0 comments on commit 4369217

Please sign in to comment.