tstehr / recipemd Goto Github PK
View Code? Open in Web Editor NEWMarkdown recipe format and cli tool
Home Page: https://recipemd.org
License: GNU Lesser General Public License v3.0
Markdown recipe format and cli tool
Home Page: https://recipemd.org
License: GNU Lesser General Public License v3.0
I use Reference-style images with a data-URL. When fed into recipemd
, the definition is gone.
Testcase test.md
:
# Title
---
- *1* Thing
---
Image: ![Step 1][step_1].
[step_1]: data:image/png;base64,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
Output of recipemd test.md
:
# Title
---
- *1* Thing
---
Image: ![Step 1][step_1].
The original idea according to @tstehr was to edit tags, but for tihs, the name is kind of misleading and the tools does several things but not very well. I suggest splitting it in two tools (for example recipemd-find and recipemd-edit).
When piping the output of recipemd to the file that it reads from, it crashes with
Traceback (most recent call last):
File "/home/bart/.local/bin/recipemd", line 11, in <module>
load_entry_point('recipemd==4.0.3', 'console_scripts', 'recipemd')()
File "/home/bart/programming/python/recipemd/.pyvenv/lib/python3.7/site-packages/recipemd/cli/main.py", line 40, in main
r = rp.parse(src)
File "/home/bart/programming/python/recipemd/.pyvenv/lib/python3.7/site-packages/recipemd/data.py", line 154, in parse
self._parse_title()
File "/home/bart/programming/python/recipemd/.pyvenv/lib/python3.7/site-packages/recipemd/data.py", line 183, in _parse_title
raise RuntimeError(f"Invalid, title (heading with level 1) required, got "
RuntimeError: Invalid, title (heading with level 1) required, got None instead
steps to reproduce:
echo '# FOO
**4 bar**
---
- *4* baz
---
FooBarBaz
' > test.md
recipemd test.md > test.md
When scaling a recipe, especially when specifying a yield, the ingredient results can become pretty unreadable.
For example:
$ recipemd -y "4 servings" -i celeriac_mushroom_and_tomato_lasagna.md
[...]
0.5714285714285714285714285714 small celeriac root
1.714285714285714285714285714 parsnip roots
0.5714285714285714285714285714 tbsp coconut oil, butter, ghee or olive oil
1.142857142857142857142857143 clove garlic
11.42857142857142857142857143 brown mushrooms
142.8571428571428571428571428 g frozen spinach, thawed (fresh is fine too)
11.42857142857142857142857143 cherry tomatoes (or 4 regular tomatoes), sliced
Also, I'm not really sure how to measure out 0.5714285714285714285714285714 tbsp
of something. I think rounding to two or three decimals should suffice for most use cases.
In #38 a did a survey of recipes that will be considered invalid once we fix the incorrect treatment of amounts without factors. On thing that came out of that is users like to mark amounts as approximate, in particular for the yield amount of a recipe.
Of the 43 recipes that had an error, 12 were instances of approximate yields. Additionally there was one recipe using an approximate ingredient amount.
The survey turned up 3 use cases:
I've also wanted a syntax like this in my own use from time to time, mostly for uses cases 2 and 3.
For syntax 9 recipes used ~
as a prefix for the amount (~3 Portionen
) the remaining 4 used ca
/ca.
. I'd also prefer using ~
independently of existing use as it is not language specific.
For the spec right now my idea is to allow an optional ~
prefix for amounts in yields and ingredients. This translates into a boolean approximate
flag added to the amount data type. The semantics of that flag would be "The recipe author has indicated that the amount is approximate". If a more detailed explanation of what that entails is required authors need to put that in the instructions.
When trying to call recipemd-tags recipes
on a folder that contains a file path longer than the window width, the matrix containing the file paths has a width of zero items, which causes an unhandled exception.
I like the idea of managing my recipes with recipemd and git ❤️ 🚀 👍 Thank you for the tool!
To be sure that only valid recipes are committed, it is convenient to use a pre-commit hook. For me, I created a hook that can be used out-of-the-box with pre-commit. Here is the repo.
Do you think it is worth mentioning in the Recommended Tools
section? If welcomed, I can submit the PR.
Users should be able to specify whether factors are displayed as fractions or as decimals
Since IngredientGroups can't be terminated, it is impossible to create a recipe where an Ingredient follows an ingredient group. It is however possible in the AST which can lead to unexpected behaviour.
Example:
Recipe(
title="Test Recipe",
ingredients=[
Ingredient(name="Ingredient 1"),
IngredientGroup(
title="Test Group",
children=[
Ingredient(name="Group Ingredient 1"),
Ingredient(name="Group Ingredient 2")
]
),
Ingredient(name="Ingredient 2"),
]
)
Serialization:
# Test Recipe
---
- Ingredient 1
## Test Group
- Group Ingredient 1
- Group Ingredient 2
- Ingredient 2
Parse Result:
Recipe(
title="Test Recipe",
ingredients=[
Ingredient(name="Ingredient 1"),
IngredientGroup(
title="Test Group",
children=[
Ingredient(name="Group Ingredient 1"),
Ingredient(name="Group Ingredient 2"),
Ingredient(name="Ingredient 2")
]
)
]
)
We need to either introduce a group terminator or disallow such ASTs.
the dependency on Commonmark is quite specific which causes issues when running in a python environment with other software.
As a long-time user of the RecipeMD format for storing and displaying my recipe collection, I am deeply grateful for your work on this project.
I am currently implementing a RecipeMD parser in Rust based on the specification, and I am encountering a few issues. In some aspects, the reference implementation and its accompanying testcases show and enforce behavior that differs from what I expect based on the specification.
(I previously created a TypeScript implementation, but since it is a direct port of the Python reference implementation, it has the exact same behavior as the latter one.)
The serialized representation (e.g. as JSON) is not specified which results in some uncertainties for testing and general interoperability problems between different implementations.
Main aspects:
ingredient_groups
vs. ingredientGroups
[]
(for list-like data types), null
or complete absence of the fieldIn the following, I use some pseudocode syntax to describe the structure/schema of different data type representations. I hope it helps to represent them language-independently (is there an actual standard for such a format?).
The reference implementation assigns ingredients without a preceding heading to a ingredients
field of the recipe. The specification only mentions a field that holds ingredient groups.
Recipe {
title: String,
description: String | None,
tags: String[1..n] | None,
yields: Amount[1..n] | None,
ingredients: IngredientGroup[1..n],
instructions: String | None,
}
Recipe {
title: String,
description: String | None,
tags: String[0..n],
yields: Amount[0..n],
ingredients: Ingredient[0..n],
ingredient_groups: IngredientGroup[0..n],
instructions: String | None,
}
The reference implementation uses nested ingredient groups based on the heading level. Such behavior is not defined by the specification.
The specification does not allow ingredient groups with no ingredients (“1..n ingredients”). The reference implementation accepts them and many testcases require them and have no ingredients at all.
IngredientGroup {
title: String | None,
ingredients: Ingredient[1..n],
}
IngredientGroup {
title: String | None,
ingredients: Ingredient[0..n],
ingredient_groups: IngredientGroup[0..n],
}
See #38.
Amount {
factor: Number,
unit: String | None,
}
Amount {
factor: Number | None,
unit: String | None,
}
Regarding serialization, it would be great if the JSON representation was formalized and a JSON Schema would be created.
Regarding nested ingredient groups, I think they make sense and should be incorporated into the specification.
Regarding empty ingredient groups, I think they are useful when drafting a recipe and should be allowed by the specification.
I am willing to discuss any changes that need to be taken, and I am happy to help where I can.
When multiplying the yield of a recipe with the -m or -s options, the amounts given in the instructions do not scale.
I suggest writing them in italic for easy parsing and emphasis in the rendered recipe.
The library and specification should have a license. I propose to license both under the LGPL.
All persons who have in the past contributed to this repository need to agree to this change. According to the repository statistics the contributors are:
Please comment below whether you agree to licensing your contribution to RecipeMD under the LGPL. I'd be happy to discuss licensing options or other concerns.
Amounts are required to have a factor, but the implementation allows factors to be None
:
Lines 50 to 51 in 3146efb
There are also testcases for amounts in ingredients and yields without a factor:
Line 3 in 3146efb
Line 30 in 3146efb
We need to decided wether factorless amounts should be allowed and then either adopt the spec or the implementation and the testcases to be consistent with that decision.
Issue spun out of the discussion in PR #46.
There's a wish from some users to add some sort of structured metadata to recipes. Examples of metadata include:
Some of these can be incorporated into tags, e.g. by adding the taste as a tag or tagging a recipe as vegeratian. But that doesn't really work for pair-shaped metadata like times or nutrition information. For these the only solution currently is to put them into the recipe description, instructions or tags in an ad-hoc format. This has the drawback that different recipe author are going to choose different formats which is not bad per se but it would be nice to have a consistent format to enable tooling support.
To figure out where recipe authors put data like this, I did an informal "survey" by looking at some cookbooks I own and recipe websites I tend to use:
From the discussion in #46 I extracted the following set of requirements that I'd like to satisfy
The overarching concern of adding a feature like this to RecipeMD is that it should feel natural and fit the way people might author their recipes in Markdown if not guided by the specification or tooling requirements.
The source text should follow the markdown design philosophy:
Markdown-formatted document should be publishable as-is, as plain text, without looking like it’s been marked up with tags or formatting instructions.
This includes using a minimal amount of additional required markup and retaining a natural reading order.
The output of running the RecipeMD file to a commonmark renderer should look reasonable (e.g. converting to HTML or to PDF for printing via pandoc).
The metadata format should allow tooling to do stuff with the metadata in a way that makes it more useful than ad-hoc metadata in the description.
If the metadata embedded in RecipeMD uses a pre-existing format, it should fit with RecipeMD in spirit and be easy to understand and author.
So far two approaches have been suggested. We're open for arguments for/against both of these and also for additional suggestions.
In #46 I considered using tags as a more generic meta data field, by allowing colon-separated key-value-pairs there:
*vegan, summer, calories/serving: 39, protein/serving: 1.3 g, sugar/serving: 5.9 g,
fat/serving: 0.4 g, active time: 45 min, total time: 2h*
In discussion with @AberDerBart an alternative approach came up. We could allow adding a fenced code block (aka ```-block) where tags and yields are currently located and parse its contents (as YAML or a something like that):
*vegan, summer*
```yaml
calories/serving: 39
protein/serving: 1.3 g
sugar/serving: 5.9 g
fat/serving: 0.4 g
active time: 45 min
total time: 2h
```
Got this idea from https://recipes.rixx.de/gebaeck/muerbeteig-kekse/
My suggestion:
- (*100g* Schokolade)
# (Puderzuckerüberguss-Dekoration)
- *100ml* Wasser
- *100g* Puderzucker
- *1* Zitrone
- (Lebensmittelfarbe)
So using braces you can mark either single ingredients or whole groups as optional.
Maybe this could also be solved using #4
This would be useful for changing the scale of a recipe, e.g.
recipemd recipe.md -m 2 > recipe.md
results in an empty file
I plan to move my recipes from my Database to markdown files. But I have information about
Theses informations are very useful but specifications not allow me to write down. Where can I put them ? Should I put them into tags ?
This is similar to #46 which was closed as supporting metadata through a YAML frontmatter was not deemed a good fit in favour of supporting pair based metadata in #47 .
However would possible for RecipeMD to simply ignore YAML frontmatter, or anything that comes before the title?
Currently I am trying to add my personal recipes to my personal notes folder, which already uses YAML frontmatter for a number of unrelated functions. For example, I would just like to be able have a creation date and a type metadata identifying it as a recipe in the frontmatter. Like this:
---
created: 2023-09-25
type: recipe
---
# Title of Recipe
I use some other Markdown processors that do use this metadata, however when I add it to my recipes I can't use RecipeMD to parse them anymore. Giving it the above document will break parsing altogether.
I don't really want you to implement anything for the metadata, just to ignore anything in a frontmatter block, or anything before the title. I don't think this would clash with any of the existing spec as a title is required anyway.
Since I upgraded to Python 3.9.1
, I get a crash when trying to use recipemd-find
or recipemd
:
$ recipemd-find tags
Traceback (most recent call last):
File "/usr/bin/recipemd-find", line 5, in <module>
from recipemd.cli.find import main
File "/home/nessie/.local/lib/python3.9/site-packages/recipemd/cli/find.py", line 23, in <module>
from recipemd.data import RecipeParser, Recipe
File "/home/nessie/.local/lib/python3.9/site-packages/recipemd/data.py", line 12, in <module>
import commonmark
File "/home/nessie/.local/lib/python3.9/site-packages/commonmark/__init__.py", line 4, in <module>
from commonmark.main import commonmark
File "/home/nessie/.local/lib/python3.9/site-packages/commonmark/main.py", line 14, in <module>
from commonmark.blocks import Parser
File "/home/nessie/.local/lib/python3.9/site-packages/commonmark/blocks.py", line 5, in <module>
from commonmark import common
File "/home/nessie/.local/lib/python3.9/site-packages/commonmark/common.py", line 14, in <module>
HTMLunescape = html.parser.HTMLParser().unescape
AttributeError: 'HTMLParser' object has no attribute 'unescape'
It should be possible to run pip install --install-option test
with a distribution. See #31 (comment) for more detail.
When a recipe links itself in the ingredients, using recipemd -f
causes an unhandled "maximum recursion depth exceeded"-exception.
I got an unhandled exception:
$ recipemd -f chili-kaese-spaetzle.md
Traceback (most recent call last):
File "/usr/bin/recipemd", line 8, in <module>
sys.exit(main())
File "/home/nessie/.local/lib/python3.9/site-packages/recipemd/cli/main.py", line 55, in main
r = _get_flattened_recipe(r, recipe_url=recipe_url, parser=rp)
File "/home/nessie/.local/lib/python3.9/site-packages/recipemd/cli/main.py", line 151, in _get_flattened_recipe
recipe = _create_flattened_substituted_ingredients(recipe, ingr_to_recipe)
File "/home/nessie/.local/lib/python3.9/site-packages/recipemd/cli/main.py", line 246, in _create_flattened_substituted_ingredients
flattened_ingr = _create_flattened_substituted_ingredients(ingr, ingr_to_recipe)
File "/home/nessie/.local/lib/python3.9/site-packages/recipemd/cli/main.py", line 254, in _create_flattened_substituted_ingredients
return replace(
TypeError: replace() missing 1 required positional argument: 'obj'
When a fenced code block is the last thing in the instructions, the closing ```
inside the recipe.instructions
property is sometimes cut off. When there are fewer than 3 characters in the the last line of the code block, only this many backticks are returned in the string. The bug doesn't happen with more content after the code block.
I think the problem is near
Lines 410 to 415 in 3146efb
# Bug
---
- *1* Thing
---
Make something.
```
abc
d
```
Have fun!
recipemd Test1.md
# Bug
---
- *1* Thing
---
Make something.
```
abc
d
```
Have fun!
# Bug
---
- *1* Thing
---
Make something.
```
abc
d
```
recipemd Test2.md
# Bug
---
- *1* Thing
---
Make something.
```
abc
d
`
There is only one backtick in the output.
# Bug
---
- *1* Thing
---
Make something.
```
abc
de
```
recipemd Test3.md
# Bug
---
- *1* Thing
---
Make something.
```
abc
de
``
There are only two backticks in the output.
# Bug
---
- *1* Thing
---
Make something.
```
abc
def
```
recipemd Test4.md
# Bug
---
- *1* Thing
---
Make something.
```
abc
def
```
Got this idea from a pizza recipe, where it specified multiple valid Flour types.
While #4 could be used to express this thing, I think that it is a bit clumsy to express such xor Relations for single ingredients.
My suggestion:
- *1kg* Weizenmehl Type 405
or *1kg* Weizenmehl Type 550
For the spec, we consider it an alternative ingredient, if it comes after a linebreak and contains a valid amount. The text before the amout is ignored.
I know that this won't work with ingredients without amount, but it was the best idea I could think of.
Also useful for fresh and dried yeast (different amounts/amount units)
Recipes should have the the ability to use other recipes as Ingredients. I propose roughly the following syntax:
# Nachos
---
- 200g [Guacamole](guacamole.md)
- 200g [Refried Beans](refried_beans.md)
There is not really a way to express variants of recipes in one recipe
Example use case: https://github.com/dasnessie/recipes/blob/master/linsenlasagne.md
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.