Skip to content
Closed
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
30 changes: 30 additions & 0 deletions .githooks/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

# To activate this commit-msg hook, save it into a .githooks subdirectory in any git repository and run the following commands from the main repository directory command shell:
# chmod a+x .githooks/commit-msg # make executable
# git config core.hooksPath ".githooks" # enable hook

# Public: Current API version in format "x.y.z".
export API_VERSION="1.0.0"

#
# Public: git commit-msg hook to reject multi-line commit messages.
#
# Checks whether git commit message is a single line or multiline and rejects multi-line commits.
# git stores commit messages in a temporary file, .git/COMMIT_EDITMSG
#
# Exits with error code 1 if multi-line commit message found

reject_multiline_commit_messages() {
# Read the commit message temporary file
commit_message=$(cat .git/COMMIT_EDITMSG)

# Check if the commit message contains one or more line breaks
if [[ "$commit_message" == *$'\n'* ]]; then
echo "Multi-line commit messages are forbidden! Try committing again."
exit 1
fi
}

reject_multiline_commit_messages # Exit with error if multi-line message found
exit 0 # Proceed with commit otherwise
25 changes: 25 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: CI / CD
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.9","3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- name: Install Python, pipenv and Pipfile packages
uses: kojoru/prepare-pipenv@v1
with:
python-version: ${{ matrix.python-version }}
- name: Turn on 'editable' mode
run: |
pipenv install -e .
pipenv install --dev
- name: Test with pytest
run: |
pipenv install pytest
pipenv --venv
pipenv run python -m pytest

18 changes: 18 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
pytest = "*"

[dev-packages]
pytest = "*"
pytest-cov = "*"
build = "*"
twine = "*"
pylint = "*"


[requires]
python_version = "3.9"
910 changes: 910 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
# Python Package Exercise
#excusegen
what is it - generates random excuses for different situations

An exercise to create a Python package, build it, test it, distribute it, and use it. See [instructions](./instructions.md) for details.
## Installation

Clone the repository and install dependencies using pipenv:

git clone https://github.com/<your-username>/excusegen.git
cd excusegen
pipenv install

Enter the virtual environment:

pipenv shell

Install the package locally:

pip install .

## Usage

Run the package from the command line:

python3 -m excusegen

Or use it in Python code:

from excusegen import generate

print(generate()) # returns a general excuse
print(generate("deadline")) # returns a deadline-related excuse
print(generate("meeting")) # returns a meeting-related excuse

Available categories:
- general
- deadline
- meeting
- class

## Testing

Run tests using pytest:

pipenv run pytest

Expected output:

==================== 1 passed in 0.00s ====================

11 changes: 11 additions & 0 deletions demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from excusegen import get_excuse, get_excuses, add_excuse

print("One random excuse:")
print(get_excuse())

print("\nAll general excuses:")
print(get_excuses("class"))

print("\nAdd a new excuse:")
add_excuse("general", "Aliens deleted my code.")
print(get_excuses("general"))
13 changes: 13 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "excusegen"
version = "0.1.0"
description = "Generates bad excuses"
authors = [
{ name = "Team Ember" }
]
readme = "README.md"
requires-python = ">=3.8"
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
pythonpath = src/
3 changes: 3 additions & 0 deletions src/excusegen/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .main import get_excuse,get_excuses, add_excuse

__all__ = ["get_excuse", "get_excuses", "add_excuse"]
17 changes: 17 additions & 0 deletions src/excusegen/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from .main import get_excuse, get_excuses

if __name__ == "__main__":
print(get_excuse())
print()
excuses = get_excuses(count = 2)
for e in excuses:
print(e)
print()
excuses = get_excuses()
for e in excuses:
print(e)
print()
excuses = get_excuses(count = 4)
for e in excuses:
print(e)

108 changes: 108 additions & 0 deletions src/excusegen/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

import random

EXCUSES = {
"deadline": [
"My code was compiling for three days.",
"The API rate-limited my motivation.",
"I accidentally scheduled my task for next week."
],
"meeting": [
"Zoom refused to unmute me.",
"I got stuck in another meeting that could have been an email.",
"My calendar app lied about the time."
],
"class": [
"My virtual environment ate my homework.",
"AI took credit for my assignment.",
"My professor’s email went to spam."
],
"general": [
"The cloud was down.",
"My laptop ran out of coffee.",
"SyntaxError: life not defined."
]
}

def get_excuse(category="general"):
"""
Return randomly chosen excuse from the selected category.

Args: category (str): type of excuse to return (deadline, meeting, class, or general)

Raises ValueError if the category doesn't exist
"""
category = category.lower()
if category not in EXCUSES:
raise ValueError("Invalid category. Try 'deadline', 'meeting', 'class', or 'general'.")
return random.choice(EXCUSES[category])

def get_excuses(category="general", count = None ):
"""
Return all excuses from the selected category if no count is given.
Otherwise, randomly return a number of excuses equal to count.
If count is greater than the number of excuses in the selected category,
repeats will be shown.

Args: category (str): type of excuse to return (deadline, meeting, class, or general)
count (int | None): number of excuses to return

Raises ValueError if the category doesn't exist
Raises TypeError if count is not an int
Raises ValueError if count less than 0
"""
category = category.lower()
if category not in EXCUSES:
raise ValueError("Invalid category. Try 'deadline', 'meeting', 'class', or 'general'.")
if count is None:
return list(EXCUSES[category])
if not isinstance(count, int):
raise TypeError("count must be an int")
if count < 0:
raise ValueError("count must be >= 0")
if count == 0:
return []
n = len(EXCUSES[category])
if count <= n:
return random.sample(EXCUSES[category], k=count)
ret = random.sample(EXCUSES[category], k=n)
ret.extend(random.choices(EXCUSES[category], k=count - n))
return ret


def add_excuse(category, excuse):
"""
Add a new excuse to an existing category.

Args:
category (str): category name ('deadline', 'meeting', 'class', or 'general')
excuse (str): text of the excuse to add

Returns:
str: the excuse that was added

Raises:
ValueError: if category not found or excuse invalid
"""
#check if it is a string or not
if not isinstance(category, str):
raise ValueError("Category must be a string.")
category = category.lower().strip()

#validate category
if category not in EXCUSES:
raise ValueError("Invalid category. Try 'deadline', 'meeting', 'class', or 'general'.")

#validate the new excuse
if not isinstance(excuse, str) or not excuse.strip():
raise ValueError("Excuse must be a non-empty string.")
excuse = excuse.strip()

if excuse in EXCUSES[category]:
print("Excuse already exists; no changes made.")
return excuse

#add this new excuse
EXCUSES[category].append(excuse)

return excuse
Loading
Loading