Compare commits

..

17 commits

Author SHA1 Message Date
2e5859342d Dockerfile: fix entrypoint
All checks were successful
ci/woodpecker/push/containers Pipeline was successful
2024-10-12 00:32:22 +02:00
ae3ee28ab4 README.md: update 2024-10-12 00:20:05 +02:00
8a9ae16ad3 .woodpecker: add 2024-10-12 00:12:20 +02:00
cc9206306a parsing: also accept short ingredients without amounts
for example 'oil' will now be allowed, previously it would have to be `1
spoon oil` when sometimes people don't know the amount
2024-10-12 00:12:09 +02:00
3e3de7ea59 pyproject.toml: add dependency required with newer lxml 2024-10-12 00:04:40 +02:00
e35bc3852b update URLs 2024-10-11 22:21:30 +02:00
7b3aa25c95 Dockerfile: use python image instead of alpine, update 2024-10-11 22:20:08 +02:00
88f801cadb show full recipe price every time with a clarification if necessary 2024-05-12 22:28:42 +00:00
8046f0d237 responsive website 2023-12-12 02:33:12 +00:00
08fce0ae16 update package name in dockerfile 2023-12-12 02:32:04 +00:00
67cb69000b fix generate-* subcommands from other directories 2023-11-30 03:09:36 +00:00
7a9a39f2fb make pricedb currency mandatory
now, if there is a price entry for an ingredient, it either has to have a currency specified or default_currency has to be set in settings.yaml
2023-11-30 03:09:36 +00:00
70432d867e update urls in pyproject.toml 2023-11-30 03:09:35 +00:00
d8569aa43e Add README.md 2023-11-30 03:09:35 +00:00
816e65a00c add warning about serve not being suitable for production 2023-11-30 03:09:34 +00:00
67bb8c2266 rename project from recipes to comfy-recipes 2023-11-30 03:09:34 +00:00
69e578dba4 allow using a single string as an ingredient instead of a dict 2023-11-28 14:26:34 +00:00
9 changed files with 137 additions and 39 deletions

View file

@ -0,0 +1,15 @@
when:
- event: push
branch: main
steps:
- name: publish
image: woodpeckerci/plugin-docker-buildx
settings:
platforms: linux/amd64,linux/arm64/v8
repo: git.comfy.city/emi/comfy-recipes
registry: git.comfy.city
tags: latest
username: ${CI_REPO_OWNER}
password:
from_secret: registry_token

View file

@ -1,12 +1,11 @@
FROM alpine:3.18 AS build
FROM python:3.13-alpine AS build
COPY . /build
WORKDIR /build
RUN apk add --no-cache python3 py3-build py3-hatchling && \
RUN pip install build hatchling && \
python3 -m build .
FROM alpine:3.18
COPY --from=build /build/dist/recipes-*-py3-none-any.whl /
RUN apk add --no-cache python3 py3-pip && \
pip install /recipes-*-py3-none-any.whl && \
apk del py3-pip
ENTRYPOINT ["/usr/bin/recipes-cli"]
FROM python:3.13-alpine
COPY --from=build /build/dist/ /mnt
RUN pip install /mnt/comfy_recipes-*-py3-none-any.whl && \
rm -r /mnt
ENTRYPOINT ["/usr/local/bin/recipes-cli"]

43
README.md Normal file
View file

@ -0,0 +1,43 @@
# Comfy Recipes
Comfy Recipes is a simple application that renders your recipes into HTML.
Documentation is available at <https://comfy.city/comfy-recipes/docs>
## Running
```sh
docker run -v ./recipes:/recipes git.comfy.city/Emi/comfy-recipes:latest build /recipes
```
## Building
### Building the python package
Make sure you have hatchling and build modules installed.
```sh
python3 -m build
```
The package can now be installed with
```sh
pip install dist/comfy-recipes-*-py3-none-any.whl
```
### Building the docker container
```sh
docker buildx build -t git.comfy.city/Emi/comfy-recipes:local .
```
The container can now be ran
```sh
docker run -v ./recipes:/recipes git.comfy.city/Emi/comfy-recipes:local build /recipes
```
### Building the documentation
```sh
cd docs
mdbook build
```
Documentation can now be served with
```sh
mdbook serve
```

View file

@ -44,12 +44,14 @@ def main() -> None:
(args.address, args.port), http.server.SimpleHTTPRequestHandler
)
print(f"serving at http://{args.address}:{args.port}")
print("THIS WEB SERVER IS ONLY MEANT FOR LOCAL TESTING, IT IS NOT SUITABLE FOR PRODUCTION")
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
elif args.subcommand == "generate-units":
if not args.force and os.path.isfile(args.directory + "/units.yaml"):
os.chdir(args.directory)
if not args.force and os.path.isfile("units.yaml"):
print(
"units.yaml already exists, pass --force if you want to overwrite it",
file=sys.stderr,
@ -58,7 +60,8 @@ def main() -> None:
else:
builder.generate_units()
elif args.subcommand == "generate-ingredients":
if not args.force and os.path.isfile(args.directory + "/ingredients.yaml"):
os.chdir(args.directory)
if not args.force and os.path.isfile("ingredients.yaml"):
print(
"ingredients.yaml already exists, pass --force if you want to overwrite it",
file=sys.stderr,

View file

@ -197,28 +197,29 @@ class IngredientInstance(Element):
def from_dict(cls, ctx: Context, dct: str | Dict[str, Any]) -> Self:
if isinstance(dct, str):
string = dct.strip()
p = re.compile(r"^(?:([0-9\.]+) ([a-zA-Z]+) )?([\w ]+)(?: \((.*)\))?$")
p = re.compile(r"^(?:([0-9\.]+) ([a-zA-Z]+) )+([\w ]+)(?: \((.*)\))?$")
match = p.match(string)
if match is None:
raise RuntimeError(
"ingredient {string} regex not matched, it should be in the format [amount(num) unit(string, one word)] name(string, any number of words) [(note(string))]"
)
amount = float(match.group(1))
unitstr = match.group(2)
amount = float(1)
unit = ctx.default_unit
if unit is not None:
unitx = ctx.units.get(unitstr)
if unitx is None:
ctx.issues.error(issues.ISSUE_UNKNOWN_UNIT, "unknown unit {unitstr}")
else:
unit = unitx
name = match.group(3)
note = match.group(4)
name = string
note = None
if match is not None:
amount = float(match.group(1))
unitstr = match.group(2)
if unit is not None:
unitx = ctx.units.get(unitstr)
if unitx is None:
ctx.issues.error(issues.ISSUE_UNKNOWN_UNIT, f"unknown unit {unitstr}")
else:
unit = unitx
name = match.group(3)
note = match.group(4)
if note is None:
note = ""
return cls(
ctx=ctx,
name=dct,
name=name,
amount=amount,
unit=unit,
alternatives=[],
@ -289,7 +290,7 @@ class Recipe(Element):
source: Optional[SafeHTML],
ingredients: List[IngredientInstance],
subrecipes: List["Recipe"],
price: Optional["PriceDB"],
price: Optional["MultiPriceDB"],
stepsections: List[StepSection],
) -> None:
super().__init__(ctx)
@ -317,7 +318,7 @@ class Recipe(Element):
rp = Recipe.from_dict(ctx, partdct)
subrecipes.append(rp)
price: Optional[PriceDB] = None
price: Optional[MultiPriceDB] = None
pricex: float = 0
ingswithprice = 0
ingswithoutprice = 0
@ -335,16 +336,22 @@ class Recipe(Element):
# we don't know how to convert currencies yet
currency = None
break
if currency is None or ingswithoutprice != 0 or len(ingredients) == 0:
if currency is None or len(ingredients) == 0:
price = None
else:
price = PriceDB(
pricedb = PriceDB(
ctx=ctx,
price=pricex,
amount=1,
unit=ctx.default_unit,
currency=currency,
)
price = MultiPriceDB(
ctx=ctx,
pricedb=pricedb,
item_count=len(ingredients),
item_prices_missing=ingswithoutprice,
)
stepsections: List[StepSection] = []
if "steps" in dct:
@ -564,7 +571,7 @@ class PriceDB(Element):
price: float,
amount: float,
unit: Unit,
currency: Optional[str],
currency: str,
) -> None:
super().__init__(ctx)
self.price = price
@ -591,4 +598,19 @@ class PriceDB(Element):
currency = ctx.settings.default_currency
if "currency" in dct:
currency = dct["currency"]
if currency is None:
raise RuntimeError("currency not specified and default_currency is also not set")
return cls(ctx=ctx, price=price, amount=amount, unit=unit, currency=currency)
class MultiPriceDB(Element):
def __init__(
self,
ctx: Context,
pricedb: PriceDB,
item_count: int,
item_prices_missing: int,
) -> None:
super().__init__(ctx)
self.pricedb = pricedb
self.item_count = item_count
self.item_prices_missing = item_prices_missing

View file

@ -1,12 +1,24 @@
{% macro price(price) -%}
{% if price != None and price is defined and price.price is defined %}{{price.price|round(1)|numprint}}{%else%}?{% endif %} {{price.currency}}
{%- endmacro %}
{% macro multiprice(multiprice, shortform) -%}
{% if multiprice != None and multiprice.pricedb != None and multiprice.pricedb is defined and multiprice.pricedb.price is defined -%}
{{multiprice.pricedb.price|round(1)|numprint}}
{%else%}
?
{%- endif %}
{{multiprice.pricedb.currency}}
{%if multiprice.item_prices_missing != 0 and not shortform%}
({{multiprice.item_prices_missing}}/{{multiprice.item_count}} prices missing)
{%endif%}
{%- endmacro %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
{% block body %}{% endblock %}

View file

@ -3,6 +3,6 @@
{%block body %}
<h1>Recipes</h1>
{% for recipe in recipes %}
<li>{% if recipe.price != None %}{{price(recipe.price)}} {%endif%}<a href="{{ recipe.outpath }}">{{ recipe.title }}</a></li>
<li>{% if recipe.price != None %}{{multiprice(recipe.price, true)}} {%endif%}<a href="{{ recipe.outpath }}">{{ recipe.title }}</a></li>
{% endfor %}
{%endblock%}

View file

@ -1,6 +1,6 @@
{% extends "base.html" %}
{% macro ingredientpart(ing) -%}
{% if recipe.price != None %}{{price(ing.price)}} {%endif%}
{% if ing.price != None %}{{price(ing.price)}} {%endif%}
{% if ing.amount != None %} {{ing.amount|amountprint}}
{% if ing.unit != None %} {{ing.unit.name}}{% endif %}
{%endif%}
@ -29,7 +29,10 @@
{% for ing in rec.ingredients %}
<li>{{ingredient(ing)}}</li>
{% endfor %}
{% if rec.price != None %}price: {{price(rec.price)}}{%endif%}
{% if rec.price != None %}
<br>
price: {{multiprice(rec.price)}}
{%endif%}
{% endif %}
{% if rec.stepsections|length != 0 %}
<h3>Steps</h3>

View file

@ -3,13 +3,13 @@ requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "recipes"
name = "comfy-recipes"
version = "0.0.1"
description = "Smart recipe static site generator"
authors = [{ name = "Emi Vasilek", email = "emi.vasilek@gmail.com" }]
keywords = ["recipes", "recipe", "static site generator"]
requires-python = ">= 3.8"
dependencies = ["jsonschema", "jinja2", "PyYAML", "mistune", "lxml"]
dependencies = ["jsonschema", "jinja2", "PyYAML", "mistune", "lxml", "lxml_html_clean"]
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python",
@ -17,9 +17,10 @@ classifiers = [
license = { file = "LICENSE" }
[project.urls]
Homepage = "https://codeberg.org/comfy.city/recipes"
Repository = "https://codeberg.org/comfy.city/recipes.git"
Issues = "https://codeberg.org/comfy.city/recipes/issues"
Homepage = "https://git.comfy.city/Emi/comfy-recipes"
Repository = "https://git.comfy.city/Emi/comfy-recipes.git"
Issues = "https://git.comfy.city/Emi/comfy-recipes/issues"
Documentation = "https://comfy.city/comfy-recipes/docs"
[project.scripts]
recipes-cli = "comfyrecipes.cli:main"