Giter Site home page Giter Site logo

cooklang / cooklang-ts Goto Github PK

View Code? Open in Web Editor NEW
68.0 6.0 6.0 274 KB

Lightweight Cooklang parser implemented in TypeScript

Home Page: https://cooklang.github.io/cooklang-ts/

License: MIT License

TypeScript 92.48% JavaScript 7.52%
cooklang parsing typescript cooking-recipes cooking markup markup-language

cooklang-ts's Introduction

Cooklang-TS

cooklang-ts logo cooklang-ts logo

Cooklang-TS is a TypeScript library for parsing and manipulating Cooklang recipes.

To-Do

  • Pass all tests
  • Add recipe scaling support

Usage

import { Recipe, Parser, getImageURL } from '@cooklang/cooklang-ts';

const source = `
>> source: https://www.dinneratthezoo.com/wprm_print/6796
>> total time: 6 minutes
>> servings: 2

Place the @apple juice{1,5%cups}, @banana{one sliced}, @frozen mixed berries{1,5%cups} and @vanilla greek yogurt{3/4%cup} in a #blender{}; blend until smooth. If the smoothie seems too thick, add a little more liquid (1/4 cup).

Taste and add @honey{} if desired. Pour into two glasses and garnish with fresh berries and mint sprigs if desired.
`;

console.log(new Recipe(source));
// {
//     ingredients: [...],
//     cookwares: [...],
//     metadata: {...},
//     steps: [
//         [...],
//         [...],
//     ],
//     shoppingList: {},
// }

console.log(new Parser().parse(source).metadata);
// {
//     source: 'https://www.dinneratthezoo.com/wprm_print/6796',
//     'total time': '6 minutes',
//     servings: '2',
// }

console.log(getImageURL('Mixed Berry Smoothie', {
    step: 1,
    extension: 'png'
}));
// 'Mixed Berry Smoothie.1.png'

Documentation

Documentation can be found here along with the Cooklang Specification.

Testing

Tests are as found in https://github.com/cooklang/spec/blob/main/tests/canonical.yaml.

npm test

cooklang-ts's People

Contributors

bpevs avatar daviesjamie avatar dubadub avatar isaacvando avatar rottenfishbone avatar szepeviktor avatar thattsguy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

cooklang-ts's Issues

[BUG] problem with cookware parsing

Describe the bug
The page https://cooklang.org/docs/spec/, section Cookware shows example where it should be possible to define cookware without the need to provide ending {}. It should work for single cookware defined by single word.

The TS parser is not respecting this specification, which was confirmed by @dubadub on Discord.

This parser should be fixed or the specification should be changed to require {}.

To Reproduce

Parse

Place the potatoes into a #pot.
Mash the potatoes with a #potato masher{}.

The problem is visible also by putting this text to https://biowaffeln.github.io/cooklang-parser/.

Expected behavior

Parser should recognize pot and potato masher as used cookware.
Now it incorrectly recognizes pot. as cookware.

Parser incorrectly detecting end of timer units

Describe the bug
Sometimes the parser fails to detect the closing } after a timer declaration, and instead parses all of the following text as a unit for that timer.

To Reproduce
This is a simple script that reproduces the behaviour:

import { Parser } from "@cooklang/cooklang-ts";

const source = `Continue frying for ~{1%minute}. Add the @garlic{1%clove}.`;
const recipe = new Parser().parse(source);
console.log(JSON.stringify(recipe, null, 2));

This outputs:

{
  "metadata": {},
  "steps": [
    [
      {
        "type": "text",
        "value": "Continue frying for "
      },
      {
        "type": "timer",
        "name": "",
        "quantity": 1,
        "units": "minute}. Add the @garlic{1%clove"
      },
      {
        "type": "text",
        "value": "."
      }
    ]
  ],
  "shoppingList": {}
}

I've tested against the published npm version (1.0.0) and the latest code on main.

Expected behavior
In the above example, I expected the timer to have a unit of minute (or minutes), and for the following text and ingredient to be parsed correctly.

[BUG] including a Markdown link under Source metadata breaks rendering

This is a cross-post from a bug in the VS Code Cook Render extension, which uses this library.

User behavior: The following code at the top of a .cook document.

>> source: [Small Town Woman](https://www.smalltownwoman.com/easy-matzo-ball-soup/)

Expected result: The following Markdown:

**source:** [Small Town Woman](https://www.smalltownwoman.com/easy-matzo-ball-soup/)

Actual result:

## Small Town Woman
- (https://www\.smalltownwoman\.com/easy\-matzo\-ball\-soup/)
- [all remaining metadata are bullet-pointed, and ingredients/steps are not rendered]

According to the extension's dev (see link above), this is because there is no way to specify to the renderer whether this file is a recipe or shopping list. When the renderer sees a [ (as in a Markdown link), it switches to shopping list mode.

[REQUEST] Timers as ISO 8601

Is your feature request related to a problem? Please describe.

https://www.schema.org/Recipe uses their Duration type for things related to time. That type uses ISO8601. It would be nice to get the values as such to be able to convert to schema.org in renderers.

Describe the solution you'd like
A getter for it would be nice

Describe alternatives you've considered
Apart from parsing it myself none.

Additional context
I guess this may need a spec change to cooklang itself as well.

Parser greedily gobbles everything between first and last inline comments

Describe the bug
If there's more than one inline comment (using [- comment -] syntax) in a recipe file, everything between the opening of the first comment and the closing of the last comment is lost when parsing the recipe file.

This bug is only present in the code on the main branch of the repository; it has not yet been released to npm.

To Reproduce
An example that reproduces the issue:

import { Parser } from "@cooklang/cooklang-ts";

const source = "Add some [- comment -] @salt and [- another comment -] @pepper";
const recipe = new Parser().parse(source);

console.log(recipe.steps);

When running this against the code in the npm release:

$ npm install @cooklang/cooklang-ts

This outputs:

[
  [
    { type: 'text', value: 'Add some ' },
    { type: 'ingredient', name: 'salt', quantity: 'some' },
    { type: 'text', value: ' and ' },
    { type: 'ingredient', name: 'pepper', quantity: 'some' }
  ]
]

However, when running against the code in the main branch:

$ npm uninstall @cooklang/cooklang-ts
$ npm install https://github.com/cooklang/cooklang-ts

This outputs:

[
  [
    { type: 'text', value: 'Add some ' },
    { type: 'ingredient', name: 'pepper' }
  ]
]

Expected behavior
I expected the salt in the recipe to not be ignored!

Additional context
I think this is almost certainly because the regex matching that strips out comments at the start of parsing is being too greedy, and is matching everything between the first comment opening character and last comment closing character.

In the short term this can probably be fixed by adjusting the regex, but I think in the long-term a more robust token-based lexer/parser algorithm could be considered (similar to what is already done in cooklang-swift) to avoid the inherent fragility of regex matching.

[BUG] Metadata is not parsed correctly with CRLF line endings

If you have CRLF ("\r\n") line endings enabled (i.e. on a windows machine), the following input:

>> source: example.com
step

parses as:

{
    "ingredients": [],
    "cookwares": [],
    "metadata": {
        "source": "example.com"
    },
    "steps": [
        [
            {
                "type": "text",
                "value": "\r"
            }
        ],
        [
            {
                "type": "text",
                "value": "step"
            }
        ]
    ],
    "shoppingList": {}
}

When LF ("\n") line endings are enabled it parses correctly as:

{
    "ingredients": [],
    "cookwares": [],
    "metadata": {
        "source": "example.com"
    },
    "steps": [
        [
            {
                "type": "text",
                "value": "step"
            }
        ]
    ],
    "shoppingList": {}
}

[BUG] Problem(s) with step number in AST

Describe the bug
A while ago I requested expanding the AST to include the step number(s) for the ingredients in a recipe. I've now realized that the cooklang specification suggests only mentioning each ingredient once:

## If you’re writing a recipe, only @mention each ingredient once.
After the first time, you can simply refer to the ingredient by name and relative quantity (e.g. “then put half the remaining bacon in the oven to crispen.”).

so because of this the step element in the AST doesn't properly work when an ingredient is used in several steps, e.g. Chop cabbage in step 1, put potatoes in the oven in step 2, combine potatoes and cabbage in step 3. With the cooklang suggested specification cabbage would only list step 1 and potatoes would only list step 2 rather than listing steps 1,3 and steps 1,2 respectively.

I realize that I can omit the cooklang spec suggestion and just add @ingredient{0} at every step but that would add ingredient 0, 0, 0 to the ingredients list when parsed.

Note while writing out the issue: I'm getting every step number to be wrong by 1 (step listed as 3 instead of 2 when ingredient listen in 2nd step)

To Reproduce
I have a recipe that repeats ingredients in several steps (tomatoes) as you can tell tomatoes only list step number 1 instead of all the steps they're needed in

import { Recipe, Parser, getImageURL } from '@cooklang/cooklang-ts';


const source = `
>> name: Stuffed Tomatoes with Rice
>> servings: 4

Preheat the #oven{} to ~{180°C}.

Cut the tops off of @tomatoes{4 large} and scoop out the insides to create a 'shell'. Keep the insides.

In a #pan{}, sauté @onions{1 chopped} and @garlic{2 cloves minced} in @olive oil{2 tbsp} until translucent.

Add the insides of the tomatoes (chopped) to the pan and cook for ~{5 minutes}.

Add @uncooked rice{1 cup} to the pan, along with @water{2 cups}, @salt{}, and @pepper{}. Allow the mixture to simmer until the rice is cooked, approximately ~{18 minutes}.

In the meantime, place the tomato 'shells' in a #baking dish{} and drizzle with a bit of olive oil, then bake for ~{10 minutes}.

Once the rice is cooked, add chopped @fresh basil{2 tbsp} and @grated Parmesan cheese{1/4 cup} to the pan and stir to combine.

Fill the pre-baked tomato shells with the rice mixture.

Return the stuffed tomatoes to the oven and bake for an additional ~{15 minutes}.

Garnish with more fresh basil and serve hot.

`;

console.log(
    new Parser({
        includeStepNumber: true
    })
        .parse(source)
        .ingredients
    );

I run it with ts-node and I get:

[
  {
    type: 'ingredient',
    name: 'tomatoes',
    quantity: '4 large',
    units: '',
    step: 3
  },
...
]

Expected behavior

Actually just noticed that I'm getting the wrong step number (3) instead of 2

[
  {
    type: 'ingredient',
    name: 'tomatoes',
    quantity: '4 large',
    units: '',
    steps: [2, 4, 6, 7]
  },
...
]

[BUG] parser regex groups undefined

Describe the bug
When parsing a string with the Parser.parse() method, groups for the parser tokens are not detected.

I have a simple page where I listen for changes in a text area and call the parse() method on the user input

  this.form.valueChanges
    .pipe(takeUntil(this.unsubscribe$), debounceTime(200))
    .subscribe((value) => {
      if (!value.content) {
        return;
      }
	  console.log(value.content);
      this.parsedRecipe = this.parser.parse(value.content)
      console.log(this.parsedRecipe);
    });

You can see the result of the two console.log statements in the console in the screenshot.
In the screenshot you can also see that the parser code fails because the regex does not capture any groups.
The tokens seem to be correctly picked up by the regex but match.groups = undefined

This is really strange as I clearly remember that I used this lib in the past and it used to work.

I tried all past published versions and they have the same identicaly problem

To Reproduce
Steps to reproduce the behavior:

  1. instantiate a parser: const parser = new Parser()
  2. parser a string: parser.parse(recipe)
  3. See wrong results

Expected behavior
I used one of the example recipes as input for the parser I would expect the parser to correcly pick up the various elements of the recipe along the lines of:

{
    "ingredients": [{
		"type": "ingredient",
		"name": "egg yoke"
		"quantity": 1
		},
		...
	],
    "cookwares": [],
    "metadata": {},
    "steps": [
        [
            {
                "type": "text",
                "value": "Crack the @egg yoke{1} into a bowl, then add the @condenced milk{125%g} and @instant coffee{3tsp}, and mix until a nice caramel is formed. Do not allow to bubble."
            }
        ],
        ...
    "shoppingList": {}
}

Screenshots
image

Additional context

"dependencies": {
    "@angular/cdk": "^15.0.0",
    "@angular/common": "^15.0.0",
    "@angular/core": "^15.0.0",
    "@angular/fire": "^7.5.0",
    "@angular/forms": "^15.0.0",
    "@angular/platform-browser": "^15.0.0",
    "@angular/platform-browser-dynamic": "^15.0.0",
    "@angular/router": "^15.0.0",
    "@capacitor/app": "4.1.1",
    "@capacitor/core": "4.6.3",
    "@capacitor/haptics": "4.1.0",
    "@capacitor/keyboard": "4.1.1",
    "@capacitor/status-bar": "4.1.1",
    "@cooklang/cooklang-ts": "1.2.3",
    "@ionic/angular": "^6.1.9",
    "@ngx-translate/core": "^14.0.0",
    "@ngx-translate/http-loader": "7.0.0",
    "ionicons": "^6.0.3",
    "rxjs": "~7.5.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.11.4"
  }

[BUG] Adding bracket in metadata parses as shoppingList

Describe the bug
Small issue, but if there is a [ in metadata, we parse a recipe as a shopping-list.
I see this as a minor bug, but want to double-check it's unexpected before I put in any work to make a fix.

To Reproduce
Steps to reproduce the behavior:

// can ignore import syntax; this is standard usage via Deno
import { Parser } from 'npm:@cooklang/cooklang-ts'; 

const parser = new Parser()

console.log(parser.parse(`
>> source: https://www.youtube.com/watch?v=oTyVtAAKPRo
>> tags: [ breakfast ]
>> description: It's scrambled egg, with kimchi, and miso soup
>> servings: 1

Crack @eggs{2} into a #bowl, and beat with chopsticks

Make @Kombu broth (or dashi)

Heat a #small pan{} to high heat with #cooking oil{}

Slowly stir eggs onto pan, cook for about ~{10-15%sec}, then remove and add @sesame oil{} and @kimchi

Stir @miso paste{} into kombu broth, and bring to boil

Pour the soup into the egg-stirring bowl, and top with chopped @scallions

`))

this will produce a result of:

{
  ingredients: [],
  cookwares: [],
  metadata: { source: "https://www.youtube.com/watch?v=oTyVtAAKPRo", tags: "" },
  steps: [],
  shoppingList: {
    " breakfast ": [
      {
        name: ">> description: It's scrambled egg, with kimchi, and miso soup",
        synonym: ""
      },
      { name: ">> servings: 1", synonym: "" }
    ]
  }
}

Expected behavior

Should parse as a recipe (with ingredients, cookwares, and steps instead of `shoppingList:

{
  ingredients: [
    { type: "ingredient", name: "eggs", quantity: 2, units: "" },
    { type: "ingredient", name: "Kombu", quantity: "some", units: "" },
    ...
    {
      type: "ingredient",
      name: "scallions",
      quantity: "some",
      units: ""
    }
  ],
  cookwares: [
    { type: "cookware", name: "bowl,", quantity: 1 },
    { type: "cookware", name: "small pan", quantity: 1 },
    { type: "cookware", name: "cooking oil", quantity: 1 }
  ],
  metadata: {
    source: "https://www.youtube.com/watch?v=oTyVtAAKPRo",
    tags: "[ breakfast ]",
    description: "It's scrambled egg, with kimchi, and miso soup",
    servings: "1"
  },
  shoppingList: {},
...
}

[REQUEST] Expanding AST

Is your feature request related to a problem? Please describe.
I would like to extend the parsing to include the recipe step for each ingredient, i.e. if aubergine is used in steps 1 and 3, have that information included in the AST, is that possible? I know this is beyond the cooklang spec but I have browsed for cooklang extensions and haven't seen anything.

Describe the solution you'd like
The step number to be included in the AST perhaps through extending the cooklang parser

Describe alternatives you've considered
I have browsed for cooklang extensions in different languages (ts, py)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.