Compare commits

..

10 commits
docs ... main

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
26 changed files with 94 additions and 446 deletions

1
.gitignore vendored
View file

@ -4,4 +4,3 @@ recipes
venv
.mypy_cache
.vscode
docs/book

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/comfy-recipes-*-py3-none-any.whl /
RUN apk add --no-cache python3 py3-pip && \
pip install /comfy-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"]

View file

@ -4,7 +4,13 @@ Comfy Recipes is a simple application that renders your recipes into HTML.
Documentation is available at <https://comfy.city/comfy-recipes/docs>
## Building the python package
## 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
@ -15,17 +21,17 @@ The package can now be installed with
pip install dist/comfy-recipes-*-py3-none-any.whl
```
## Building the docker container
### Building the docker container
```sh
docker build -t codeberg.org/comfy.city/comfy-recipes .
docker buildx build -t git.comfy.city/Emi/comfy-recipes:local .
```
The container can now be ran
```sh
docker run --rm codeberg.org/comfy.city/comfy-recipes
docker run -v ./recipes:/recipes git.comfy.city/Emi/comfy-recipes:local build /recipes
```
## Building the documentation
### Building the documentation
```sh
cd docs
mdbook build

View file

@ -197,23 +197,24 @@ 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(
@ -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:
@ -594,3 +601,16 @@ class PriceDB(Element):
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

@ -1,13 +0,0 @@
[book]
authors = ["Emi Vasilek"]
language = "en"
multilingual = false
title = "Comfy Recipes Documentation"
[build]
create-missing = true
[output.html]
preferred-dark-theme = "Ayu"
git-repository-url = "https://codeberg.org/comfy.city/comfy-recipes"
git-repository-icon = "fa-git"

View file

@ -1,15 +0,0 @@
title: Swedish Pancakes
ingredients:
- 3 piece egg
- 1.25 cup milk
- 0.75 cup all purpose flour
- 1 tablespoon white sugar
- 1 tablespoon butter
steps:
- Beat eggs in a bowl until the mixture is smooth.
- Add milk
- Mix flour, sugar and salt in a separate bowl
- Mix together with the egg mixture, mix until it's smooth
- heat a griddle
- Drop just enough of the mixture to the griddle to spread to all corners
- After about a minute, turn the pancake over

View file

@ -1 +0,0 @@
- title: Pancakes

View file

@ -1,5 +0,0 @@
- title: Pancakes
ingredients:
- 100 gram flour
steps:
- make pancakes

View file

@ -1,14 +0,0 @@
# Summary
- [Introduction](./introduction.md)
- [Installation](./installation.md)
- [Tutorial](./tutorial/index.md)
- [Quick Start](./tutorial/quickstart.md)
- [Units](./tutorial/units.md)
- [Ingredients](./tutorial/ingredients.md)
- [Prices and Conversions](./tutorial/pricesconversions.md)
- [Reference](./reference/index.md)
- [Recipe (recipes/*.yaml)](./reference/recipe.md)
- [Units (units.yaml)](./reference/units.md)
- [Ingredients (ingredients.yaml)](./reference/ingredients.md)
- [Settings (settings.yaml)](./reference/settings.md)

View file

@ -1,14 +0,0 @@
# Installation
## Docker
The containers are available on <https://codeberg.org/comfy.city/-/packages/container/comfy-recipes/latest>
```sh
docker run --rm -v path/to/myrecipes:/mnt codeberg.org/comfy.city/comfy-recipes ARGUMENTS
```
for example:
```sh
docker run --rm -v path/to/myrecipes:/mnt codeberg.org/comfy.city/comfy-recipes build /mnt
```
---
Docker is currently the only supported installation method, if you would like to use another method, please refer to the building instructions which are available in the project [README.md](https://codeberg.org/comfy.city/comfy-recipes/src/branch/main/README.md)

View file

@ -1,22 +0,0 @@
# Introduction
Comfy Recipes is a program that allows you to create a rich collection of recipes and create a presentable website from them.
## Get started
If you don't have comfyrecipes installed yet, please see [Installation](./installation.md)
1. write a recipe in a yaml format to `myrecipes/recipes/xxxx.yaml`, for example
`🗎 myrecipes/recipes/pancakes.yaml:`
```yaml
{{#include ../examples/firstrealrecipe.yaml}}
```
2. run `comfyrecipes build myrecipes/` (this will build the website to `myrecipes/out/html`)
3. run `comfyrecipes serve` myrecipes and navigate to <http://localhost:8000> (this will run a web server and allow you to see your rendered recipe)
## Links
* Repository: <https://codeberg.org/comfy.city/comfy-recipes>
* Issues: <https://codeberg.org/comfy.city/comfy-recipes/issues>
* Website: <https://comfy.city/comfy-recipes>
* Documentation: <https://comfy.city/comfy-recipes/docs>

View file

@ -1,15 +0,0 @@
# Reference
* What's not marked as **MANDATORY** is optional.
## Data Types
* `string` - text of arbitrary length
* `integer` - a whole number
* `float` - a decimal number
* `number` - `integer` or `float`
## Table Of Contents
* [Recipes](recipe.md) - documenting recipes/*.yaml
* [Units](units.md) - documenting units.yaml
* [Ingredients](ingredients.md) - documenting ingredients.yaml
* [Settings](settings.md) - documenting settings.yaml

View file

@ -1,20 +0,0 @@
# Ingredients (ingredients.yaml)
- list of [Ingredient](#ingredient)
## Ingredient
- name, **MANDATORY**, string
- aliases, list of strings
- wdid, integer
- prices, list of [Prices](#price)
- conversions, list of [Conversions](#conversion)
## Price
- price, **MANDATORY**, number
- amount, **MANDATORY**, number
- unit, **MANDATORY**, string, unit has to be listed in units.yaml if units.yaml exists
- currency, string
## Conversion
- from, **MANDATORY**, string, from has to be listed in units.yaml if units.yaml exists
- to, **MANDATORY**, string, to has to be listed in units.yaml if units.yaml exists
- ratio, **MANDATORY**, number

View file

@ -1,54 +0,0 @@
# Recipe (recipes/*.yaml)
- title - **MANDATORY**, string
- ingredients - list of [Simplified Ingredients](#simplified-ingredient) and [Ingredients](#ingredient)
- steps - list of strings and [Steps Sections](#steps-section)
- subrecipes - list of [Recipes](#recipe-recipesyaml)
## Simplified Ingredient
= string in a specific format
```
[amount unit] ingredient name [(note)]
```
[] means these sections are optional
* `amount` - number
* `unit` - a single word string
* `name` - string with arbitrary content
* `note` - string with arbitrary content, but has to be enclosed in parentheses, otherwise it will be considered to be part of `name`
for example, valid values are:
```
1 piece carrot (sliced)
^ amount
^---^ unit
^----^ ingredient name
^-----^ note
```
```
200 gram green onion
^-^ amount
^--^ unit
^---------^ ingredient name
```
```
apple
^---^ ingredient name
```
```
apple (red)
^---^ ingredient name
^---^ note
```
When amount and unit is not in the string, it is assumed to be `1 piece`.
## Ingredient
- name - **MANDATORY**, string, name has to be listed in ingredients.yaml if ingredients.yaml exists
- amount - number
- unit - string, unit has to be listed in units.yaml if units.yaml exists
- or - list of [Ingredient](#ingredient)
- note - string
## Steps Section
- section - **MANDATORY**, string, section name
- steps - list of strings

View file

@ -1,2 +0,0 @@
# Settings (settings.yaml)
* default_currency, string

View file

@ -1,13 +0,0 @@
# Units (units.yaml)
- list of [Units](#unit)
## Unit
- name, **MANDATORY**, string
- conversions, list of [Conversions](#conversion)
- aliases, list of strings
## Conversion
- to, **MANDATORY**, string, to has to be listed in units.yaml if units.yaml exists
- ratio, **MANDATORY**, number
(this is similar to [Ingredients Conversion](./ingredients.md#conversion), but the `from` field is automatically set to unit's name)

View file

@ -1,21 +0,0 @@
# Tutorial
This is the full file structure of comfy recipes input data:
```
- myrecipes/ - can be named according to your preference
- recipes/ - directory that will hold the recipes
- pancakes.yaml - the individual recipe
- units.yaml - OPTIONAL, file containing all valid units and their properties
- ingredients.yaml - OPTIONAL, file containing all valid ingredients and their properties
- settings.yaml - OPTIONAL, file for overriding default settings
```
In the following sub-chapters we will first create a simple recipe and then find out what each file does and what functionalities they can offer.
- [Quick Start](./quickstart.md) - Creating simple recipes
- [Units](./units.md) - What units.yaml is for and how to use it
- [Ingredients](./ingredients.md) - Why list ingredients in ingredients.yaml and what it can offer
- [Prices and Conversions](./pricesconversions.md) - Let's start adding and calculating prices of recipes

View file

@ -1,54 +0,0 @@
# Ingredients
Similar to `units.yaml`, also `ingredients.yaml` is not a mandatory file, but having it gives us several advantages.
* spelling mistakes are not silently ignored, if something is not listed in ingredients.yaml, comfyrecipes will warn you
* we can use aliases - different names referring the same ingredient
* we will be able to add some more data to ingredients that will allow us to calculate prices (described in [Prices and Conversions](./pricesconversions.md))
```
- myrecipes/
- recipes/
- pancakes.yaml
...
- ingredients.yaml (we will be creating this file)
```
We will again use the final recipe from [Quick Start](quickstart.md).
`🗎 myrecipes/recipes/pancakes.yaml:`
```yaml
{{#include ../../examples/firstrealrecipe.yaml}}
```
If you don't want to write the file manually, we can generate it using:
```sh
$ comfyrecipes generate-ingredients
```
This will generate a minimal ingredients.yaml:
`🗎 myrecipes/ingredients.yaml:`
```yaml
- name: all purpose flour
- name: butter
- name: egg
- name: milk
- name: white sugar
```
Now, let's say we would want to call `all purpose flour` just `flour`, we can add an alias:
`🗎 myrecipes/ingredients.yaml:`
```yaml
- name: all purpose flour
aliases:
- flour
- name: butter
- name: egg
- name: milk
- name: white sugar
```
Now whenever we add an ingredient called flour, it will be a reference to `all purpose flour`.
For a full reference for what an ingredients yaml can contain, please see the [Ingredients Reference](../reference/ingredients.md)

View file

@ -1,27 +0,0 @@
# Prices and Conversions
ComfyRecipes has the ability to compute recipe prices if we give it enough information.
This requires your recipe collection to have an [`ingredients.yaml` file](./ingredients.md).
Like with the previous sections, we will use
# Settings
Sometimes we need to configure some defaults globally, this is what settings.yaml is for.
Currently it's only used for setting the default_currency.
Let's create settings.yaml and set the default currency to USD.
```
- myrecipes/
- recipes/
- pancakes.yaml
...
- settings.yaml
```
`🗎 myrecipes/settings.yaml:`
```yaml
default_currency: USD
```
For a full reference for what a settings yaml can contain, please see the [Settings Reference](../reference/settings.md)

View file

@ -1,65 +0,0 @@
# Quick Start
Let's start writing recipes. First, create the directory structure that will hold all our input data.
```
- myrecipes/
- recipes/
- pancakes.yaml
```
Now when we have the structure created, let's create the most minimal possible `pancakes.yaml`:
`🗎 myrecipes/recipes/pancakes.yaml:`
```yaml
{{#include ../../examples/minimal.yaml}}
```
And render it by running:
```sh
$ comfyrecipes build myrecipes
```
This will create a new directory `out/` in `myrecipes/` containing the built data.
```
- myrecipes/
- out/
- html/
- index.html
- pancakes.html
- recipes/
- pancakes.yaml
```
We can see the result if we start a web server pointing to that directory, for example by using:
```sh
$ comfyrecipes serve myrecipes
```
and navigate to <http://127.0.0.1:8000/>
`serve` should NOT be used in production.
While that was a valid recipe, it's not very useful and we can only see the title. Let's improve that:
`🗎 myrecipes/recipes/pancakes.yaml:`
```yaml
{{#include ../../examples/minimalusable.yaml}}
```
* each ingredient is a step in the format `amount unit name`
* `amount` has to be a number
* `unit` has to be a single word
* `name` can be a string with arbitrary content
* for a full description of this format, please see the [Reference section](../reference/recipe.md#simplified-ingredient)
* each step is a string with arbitrary content
And again, build, make sure the server is running and navigate to <http://127.0.0.1:8000/>. From now on, we will assume you know how to build and serve your output directory.
Once more, let's improve the recipe once again to something that can actually be made and is not just a demo. This does not introduce any new concepts compared to the last recipe we wrote.
`🗎 myrecipes/recipes/pancakes.yaml:`
```yaml
{{#include ../../examples/firstrealrecipe.yaml}}
```
For a full reference for what a recipe yaml can contain, please see the [Recipe Reference](../reference/recipe.md)

View file

@ -1,51 +0,0 @@
# Units
Each recipe ingredient has a string unit assigned to it. This is great, but a centralized list of all alowed units with some additional properties has several advantages:
* spelling mistakes are not silently ignored, if you make a mistake in the ingredient unit name, comfyrecipes will warn you that it's not on the units list
* `gram` and `g` can refer to the same unit (using aliases)
* we can tell comfyrecipes unit conversion rates (for example 1000 gram = 1 kilogram) which it can then use for example for calculating prices
This example will use the final example from [Quick Start](quickstart.md).
`🗎 myrecipes/recipes/pancakes.yaml:`
```yaml
{{#include ../../examples/firstrealrecipe.yaml}}
```
```
- myrecipes/
- recipes/
- pancakes.yaml
...
- units.yaml (we will be creating this file)
```
If you don't want to write the file manually, we can generate it using:
```sh
$ comfyrecipes generate-units
```
This will generate a minimal units.yaml:
`🗎 myrecipes/units.yaml:`
```yaml
- name: cup
- name: piece
- name: tablespoon
```
Now, let's say we would want to call `tablespoon` just `tbsp`, we can add an alias:
`🗎 myrecipes/units.yaml:`
```yaml
- name: cup
- name: piece
- name: tablespoon
aliases:
- tbsp
```
Now we can rename the `tablespoon` unit in the recipe to `tbsp` and it will reference the `tablespoon` unit.
For a full reference for what a recipe yaml can contain, please see the [Units Reference](../reference/units.md)

View file

@ -9,7 +9,7 @@ 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,9 @@ classifiers = [
license = { file = "LICENSE" }
[project.urls]
Homepage = "https://codeberg.org/comfy.city/comfy-recipes"
Repository = "https://codeberg.org/comfy.city/comfy-recipes.git"
Issues = "https://codeberg.org/comfy.city/comfy-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]