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
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on:
pull_request:
branches: [ pipfile-experiment ]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test-and-build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.12"]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip

- name: Upgrade pip and install build tools
run: |
python -m pip install --upgrade pip
python -m pip install build pytest pipenv

- name: Install dependencies with Pipenv
run: |
pipenv install --dev --ignore-pipfile
pipenv run pip install -e .

- name: Run tests
run: |
pipenv run pytest -q

- name: Build package (sdist & wheel)
run: |
pipenv run python -m build

- name: Upload dist artifacts
uses: actions/upload-artifact@v4
with:
name: pyflirt-dist-${{ matrix.python-version }}
path: dist/*
15 changes: 15 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]

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

[requires]
python_version = "3.12"
python_full_version = "3.12.4"
392 changes: 392 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyflirt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

41 changes: 41 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "pyflirt"
description = "A lighthearted Python package for developer-themed pickup lines."
version = "0.1.0"
authors = [
{ name = "Daniel", email = "dl4458@nyu.edu" },
]
license = { file = "LICENSE" }
readme = "README.md"
keywords = ["fun", "pickup lines", "developer humor"]
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]

dependencies = []

[project.optional-dependencies]
dev = ["pytest"]

[project.urls]
"Homepage" = "https://github.com/swe-students-fall2025/3-python-package-team_quartz"
"Repository" = "https://github.com/swe-students-fall2025/3-python-package-team_quartz.git"
"Bug Tracker" = "https://github.com/swe-students-fall2025/3-python-package-team_quartz/issues"

[tool.setuptools]
package-dir = {"" = "src"}

[tool.setuptools.packages.find]
where = ["src"]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
23 changes: 23 additions & 0 deletions src/pyflirt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
pyflirt 💘

APIs:
- line(category, name, cheese, seed)
- lines(n, categories, name, cheese, seed)
- compliment(role, mood, name, emojis, seed)
- rate_line(text, metric, seed)
"""
from .api import line, lines, categories, compliment

from .core import (
compliment,
)

__all__ = [
"compliment",
"line",
"lines",
"categories",
]

__version__ = "0.1.0"
149 changes: 149 additions & 0 deletions src/pyflirt/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import random
from typing import List, Optional
from .data import BANK, COMPLIMENT_TEMPLATES, categories as _categories
from typing import cast, Dict
import os, textwrap
from typing import Literal


__all__ = ["line", "lines", "categories"]

def categories() -> List[str]:
"""Return a sorted list of all available pickup line categories."""
return _categories()

def _check_cat(cat: Optional[str]) -> Optional[str]:
if cat is None:
return None
if cat not in BANK:
valid = ", ".join(_categories())
raise ValueError(f"Unknown category {cat!r}. Choose from {valid}.")
return cat

def _pool(cat: Optional[str], cheese: int) -> List[dict]:
"""Return candidate lines filtered by category and cheese.
If filtering wipes everything out, fall back to the unfiltered pool.
"""
def ok(e: dict) -> bool:
return e.get("cheese", 3) <= cheese

if cat is None:
picks = [e for items in BANK.values() for e in items if ok(e)]
else:
picks = [e for e in BANK[cat] if ok(e)]

if not picks:
picks = BANK[cat] if cat else [e for items in BANK.values() for e in items]
return picks

def _with_name(text: str, name: Optional[str]) -> str:
if "{name}" in text:
return text.replace("{name}", name or "you")
return text

def line(
category: Optional[str] = "nerdy",
name: Optional[str] = None,
cheese: int = 2,
seed: Optional[int] = None,
) -> str:
"""Return one random pickup line for the given category, name, and cheese level."""
if not 1 <= int(cheese) <= 5:
raise ValueError("cheese must be in 1..5")
category = _check_cat(category)
rng = random.Random(seed)
choice = rng.choice(_pool(category, cheese))
return _with_name(choice["text"], name)

def lines(
n: int = 5,
category: Optional[str] = None,
name: Optional[str] = None,
cheese: int = 2,
seed: Optional[int] = None,
) -> List[str]:
"""Return a list of n pickup lines matching the given category and cheese level."""
if n <= 0:
return []
if not 1 <= int(cheese) <= 5:
raise ValueError("cheese must be in 1..5")
category = _check_cat(category)
rng = random.Random(seed)
pool = _pool(category, cheese)

out: List[str] = []
if len(pool) >= n:
for e in rng.sample(pool, n):
out.append(_with_name(e["text"], name)) # type: ignore[index]
return out

for _ in range(n):
e = rng.choice(pool)
out.append(_with_name(e["text"], name))
return out

def compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None):
"""Return a list of n pickup lines matching the given category and cheese level."""
rng = random.Random(seed) if seed is not None else random

role = role.lower()
mood = mood.lower()
if role not in COMPLIMENT_TEMPLATES:
raise ValueError(f"Unknown role '{role}'. Choose from {list(COMPLIMENT_TEMPLATES.keys())}.")
if mood not in COMPLIMENT_TEMPLATES[role]:
raise ValueError(f"Unknown mood '{mood}'. Choose from {list(COMPLIMENT_TEMPLATES[role].keys())}.")

template = rng.choice(COMPLIMENT_TEMPLATES[role][mood])
name_bit = f", {name}" if name else ""
text = template.format(name_bit=name_bit)

if emojis > 0:
text += " " + "💖" * emojis
return text
def stylize(
text: str,
*,
width: int | None = None,
uppercase: bool = False,
color: Literal["auto", "none", "magenta", "cyan", "green"] = "auto",
) -> str:
"""
Post-process a string with wrapping, casing, and ANSI color.
- width: wrap to N columns (None = no wrap)
- uppercase: True to SHOUT
- color: 'auto' enables color only on TTY; or force 'magenta'/'cyan'/'green'/'none'
"""
s = text.upper() if uppercase else text
if width:
s = "\n".join(textwrap.wrap(s, width=width))

palette = {"magenta": "\033[95m", "cyan": "\033[96m", "green": "\033[92m"}
reset = "\033[0m"

if color == "none":
return s
if color == "auto":
color = "magenta" if os.getenv("TERM") else "none"
if color in palette:
return f"{palette[color]}{s}{reset}"
return s
def say(
*,
category: Optional[str] = "nerdy",
name: Optional[str] = None,
cheese: int = 2,
seed: Optional[int] = None,
width: int | None = None,
uppercase: bool = False,
color: Literal["auto", "none", "magenta", "cyan", "green"] = "auto",
emojis: int = 0,
) -> str:
"""
Generate a line, optionally decorate it (wrap/case/color), print it, and return it.
"""
txt = line(category=category, name=name, cheese=cheese, seed=seed)
if emojis > 0:
txt += " " + "💘" * emojis
pretty = stylize(txt, width=width, uppercase=uppercase, color=color)
print(pretty)
return pretty
102 changes: 102 additions & 0 deletions src/pyflirt/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# pyflirt/data.py
BANK = {
"nerdy": [
{"text": "Are you made of copper and tellurium? Because you’re Cu-Te.", "cheese": 2},
{"text": "Are you a quantum tunnel? Because you went straight through my barriers.", "cheese": 3},
{"text": "Is your name Wi-Fi? Because I feel a strong connection.", "cheese": 3},
{"text": "Are you a neural net, {name}? Because I keep overfitting to you.", "cheese": 4},
],
"poetic": [
{"text": "{name}, shall I compare thee to a stable release? Thou art rarer and far more dependable.", "cheese": 4},
{"text": "I’d cross the version gulf for thee, and tag a release upon thy smile.", "cheese": 3},
],
"cs": [
{"text": "If love were a bug, I’d still refuse to close your ticket.", "cheese": 1},
{"text": "Do you believe in love at first compile, {name}, or should I re-run?", "cheese": 2},
{"text": "You must be Git—my heart commits to you.", "cheese": 2},
],
"math": [
{"text": "You must be my limit—I’m approaching you from every direction.", "cheese": 4},
{"text": "If we were vectors, we’d be perfectly aligned.", "cheese": 2},
{"text": "Are you √-1? You’re unreal—and I can’t stop imagining us.", "cheese": 4},
{"text": "We are coprime; the only common divisor is one heart.", "cheese": 3},
],
"classic": [
{"text": "Are you a magician? Because whenever I look at you, everyone else disappears.", "cheese": 2},
],
}

COMPLIMENT_TEMPLATES = {
"developer": {
"sweet": [
"Your code is cleaner than a freshly cloned repo{name_bit}",
"You commit kindness with every push{name_bit}",
"You’re the pull request everyone approves instantly{name_bit}",
],
"cheeky": [
"You refactor hearts, not just code{name_bit}",
"You’ve got more charm than a recursive function{name_bit}",
"You must be a keyboard shortcut—because you’re my type{name_bit}",
],
"nerdy": [
"You debug my sadness faster than VSCode{name_bit}",
"You’re the semicolon that completes my statement{name_bit}",
"If beauty were an algorithm, you’d be O(1){name_bit}",
],
},
"designer": {
"sweet": [
"Your aesthetic sense brightens every UI{name_bit}",
"You bring color theory to my grayscale days{name_bit}",
"Pixels align themselves just to please you{name_bit}",
],
"cheeky": [
"You must be a vector—because you’ve got direction{name_bit}",
"Are you a grid system? Because my heart is well-aligned{name_bit}",
"You kerningly complete me{name_bit}",
],
"nerdy": [
"You optimize whitespace like a legend{name_bit}",
"Your Figma files are pure poetry{name_bit}",
"Even Helvetica blushes when you walk in{name_bit}",
],
},
"manager": {
"sweet": [
"You lead with empathy{name_bit}",
"Your standups make Mondays bearable{name_bit}",
"You’re the reason meetings actually end early{name_bit}",
],
"cheeky": [
"You manage hearts better than timelines{name_bit}",
"You’re my favorite deliverable{name_bit}",
"You’ve got more charisma than a sprint demo{name_bit}",
],
"nerdy": [
"You allocate my attention like a well-balanced backlog{name_bit}",
"KPIs envy your energy{name_bit}",
"Your OKRs? Outrageously Kind & Radiant{name_bit}",
],
},
"data": {
"sweet": [
"You turn noise into beauty{name_bit}",
"Every dataset wishes it were as clean as your heart{name_bit}",
"You make outliers feel included{name_bit}",
],
"cheeky": [
"You must be a correlation—because you complete my regression{name_bit}",
"You’re my favorite variable{name_bit}",
"You pivot-table my emotions{name_bit}",
],
"nerdy": [
"Your confidence interval? 100%{name_bit}",
"You’re statistically significant in my life{name_bit}",
"Your curves fit any model{name_bit}",
],
},
}

def categories():
"""Return a sorted list of all available pickup line categories."""
return sorted(BANK.keys())
Loading
Loading