cooklang 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +35 -0
  3. data/.gitignore +12 -0
  4. data/.qlty/.gitignore +7 -0
  5. data/.qlty/configs/.yamllint.yaml +21 -0
  6. data/.qlty/qlty.toml +101 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +340 -0
  9. data/Gemfile +6 -0
  10. data/Gemfile.lock +84 -0
  11. data/README.md +10 -5
  12. data/Rakefile +12 -0
  13. data/cooklang.gemspec +35 -0
  14. data/lib/cooklang/builders/recipe_builder.rb +64 -0
  15. data/lib/cooklang/builders/step_builder.rb +76 -0
  16. data/lib/cooklang/lexer.rb +5 -14
  17. data/lib/cooklang/parser.rb +24 -653
  18. data/lib/cooklang/parsers/cookware_parser.rb +133 -0
  19. data/lib/cooklang/parsers/ingredient_parser.rb +179 -0
  20. data/lib/cooklang/parsers/timer_parser.rb +135 -0
  21. data/lib/cooklang/processors/element_parser.rb +45 -0
  22. data/lib/cooklang/processors/metadata_processor.rb +129 -0
  23. data/lib/cooklang/processors/step_processor.rb +208 -0
  24. data/lib/cooklang/processors/token_processor.rb +104 -0
  25. data/lib/cooklang/recipe.rb +25 -15
  26. data/lib/cooklang/step.rb +12 -2
  27. data/lib/cooklang/timer.rb +3 -1
  28. data/lib/cooklang/token_stream.rb +130 -0
  29. data/lib/cooklang/version.rb +1 -1
  30. data/spec/comprehensive_spec.rb +179 -0
  31. data/spec/cooklang_spec.rb +38 -0
  32. data/spec/fixtures/canonical.yaml +837 -0
  33. data/spec/formatters/text_spec.rb +189 -0
  34. data/spec/integration/canonical_spec.rb +211 -0
  35. data/spec/lexer_spec.rb +357 -0
  36. data/spec/models/cookware_spec.rb +116 -0
  37. data/spec/models/ingredient_spec.rb +192 -0
  38. data/spec/models/metadata_spec.rb +241 -0
  39. data/spec/models/note_spec.rb +65 -0
  40. data/spec/models/recipe_spec.rb +171 -0
  41. data/spec/models/section_spec.rb +65 -0
  42. data/spec/models/step_spec.rb +236 -0
  43. data/spec/models/timer_spec.rb +173 -0
  44. data/spec/parser_spec.rb +398 -0
  45. data/spec/spec_helper.rb +23 -0
  46. data/spec/token_stream_spec.rb +278 -0
  47. metadata +162 -6
@@ -0,0 +1,837 @@
1
+ version: 7
2
+ tests:
3
+ testBasicDirection:
4
+ source: |
5
+ Add a bit of chilli
6
+ result:
7
+ steps:
8
+ -
9
+ - type: text
10
+ value: "Add a bit of chilli"
11
+ metadata: {}
12
+
13
+
14
+ testComments:
15
+ source: |
16
+ -- testing comments
17
+ result:
18
+ steps: []
19
+ metadata: {}
20
+
21
+
22
+ testCommentsAfterIngredients:
23
+ source: |
24
+ @thyme{2%sprigs} -- testing comments
25
+ and some text
26
+ result:
27
+ steps:
28
+ -
29
+ - type: ingredient
30
+ name: "thyme"
31
+ quantity: 2
32
+ units: "sprigs"
33
+ - type: text
34
+ value: " and some text"
35
+ metadata: {}
36
+
37
+
38
+ testCommentsWithIngredients:
39
+ source: |
40
+ -- testing comments
41
+ @thyme{2%sprigs}
42
+ result:
43
+ steps:
44
+ -
45
+ - type: ingredient
46
+ name: "thyme"
47
+ quantity: 2
48
+ units: "sprigs"
49
+ metadata: {}
50
+
51
+
52
+ testDirectionsWithDegrees:
53
+ source: |
54
+ Heat oven up to 200°C
55
+ result:
56
+ steps:
57
+ -
58
+ - type: text
59
+ value: "Heat oven up to 200°C"
60
+ metadata: {}
61
+
62
+
63
+ testDirectionsWithNumbers:
64
+ source: |
65
+ Heat 5L of water
66
+ result:
67
+ steps:
68
+ -
69
+ - type: text
70
+ value: "Heat 5L of water"
71
+ metadata: {}
72
+
73
+
74
+ testDirectionWithIngredient:
75
+ source: |
76
+ Add @chilli{3%items}, @ginger{10%g} and @milk{1%l}.
77
+ result:
78
+ steps:
79
+ -
80
+ - type: text
81
+ value: "Add "
82
+ - type: ingredient
83
+ name: "chilli"
84
+ quantity: 3
85
+ units: "items"
86
+ - type: text
87
+ value: ", "
88
+ - type: ingredient
89
+ name: "ginger"
90
+ quantity: 10
91
+ units: "g"
92
+ - type: text
93
+ value: " and "
94
+ - type: ingredient
95
+ name: "milk"
96
+ quantity: 1
97
+ units: "l"
98
+ - type: text
99
+ value: "."
100
+ metadata: {}
101
+
102
+
103
+ testEquipmentMultipleWords:
104
+ source: |
105
+ Fry in #frying pan{}
106
+ result:
107
+ steps:
108
+ -
109
+ - type: text
110
+ value: "Fry in "
111
+ - type: cookware
112
+ name: "frying pan"
113
+ quantity: 1
114
+ metadata: {}
115
+
116
+
117
+ testEquipmentMultipleWordsWithLeadingNumber:
118
+ source: |
119
+ Fry in #7-inch nonstick frying pan{ }
120
+ result:
121
+ steps:
122
+ -
123
+ - type: text
124
+ value: "Fry in "
125
+ - type: cookware
126
+ name: "7-inch nonstick frying pan"
127
+ quantity: 1
128
+ metadata: {}
129
+
130
+
131
+ testEquipmentMultipleWordsWithSpaces:
132
+ source: |
133
+ Fry in #frying pan{ }
134
+ result:
135
+ steps:
136
+ -
137
+ - type: text
138
+ value: "Fry in "
139
+ - type: cookware
140
+ name: "frying pan"
141
+ quantity: 1
142
+ metadata: {}
143
+
144
+
145
+ testEquipmentOneWord:
146
+ source: |
147
+ Simmer in #pan for some time
148
+ result:
149
+ steps:
150
+ -
151
+ - type: text
152
+ value: "Simmer in "
153
+ - type: cookware
154
+ name: "pan"
155
+ quantity: 1
156
+ - type: text
157
+ value: " for some time"
158
+ metadata: {}
159
+
160
+
161
+ testEquipmentQuantity:
162
+ source: |
163
+ #frying pan{2}
164
+ result:
165
+ steps:
166
+ -
167
+ - type: cookware
168
+ name: "frying pan"
169
+ quantity: 2
170
+ metadata: {}
171
+
172
+
173
+ testEquipmentQuantityOneWord:
174
+ source: |
175
+ #frying pan{three}
176
+ result:
177
+ steps:
178
+ -
179
+ - type: cookware
180
+ name: "frying pan"
181
+ quantity: three
182
+ metadata: {}
183
+
184
+
185
+ testEquipmentQuantityMultipleWords:
186
+ source: |
187
+ #frying pan{two small}
188
+ result:
189
+ steps:
190
+ -
191
+ - type: cookware
192
+ name: "frying pan"
193
+ quantity: two small
194
+ metadata: {}
195
+
196
+
197
+ testFractions:
198
+ source: |
199
+ @milk{1/2%cup}
200
+ result:
201
+ steps:
202
+ -
203
+ - type: ingredient
204
+ name: "milk"
205
+ quantity: 0.5
206
+ units: "cup"
207
+ metadata: {}
208
+
209
+
210
+ testFractionsInDirections:
211
+ source: |
212
+ knife cut about every 1/2 inches
213
+ result:
214
+ steps:
215
+ -
216
+ - type: text
217
+ value: "knife cut about every 1/2 inches"
218
+ metadata: {}
219
+
220
+
221
+ testFractionsLike:
222
+ source: |
223
+ @milk{01/2%cup}
224
+ result:
225
+ steps:
226
+ -
227
+ - type: ingredient
228
+ name: "milk"
229
+ quantity: "01/2"
230
+ units: "cup"
231
+ metadata: {}
232
+
233
+
234
+ testFractionsWithSpaces:
235
+ source: |
236
+ @milk{1 / 2 %cup}
237
+ result:
238
+ steps:
239
+ -
240
+ - type: ingredient
241
+ name: "milk"
242
+ quantity: 0.5
243
+ units: "cup"
244
+ metadata: {}
245
+
246
+
247
+ testIngredientMultipleWordsWithLeadingNumber:
248
+ source: |
249
+ Top with @1000 island dressing{ }
250
+ result:
251
+ steps:
252
+ -
253
+ - type: text
254
+ value: "Top with "
255
+ - type: ingredient
256
+ name: "1000 island dressing"
257
+ quantity: "some"
258
+ units: ""
259
+ metadata: {}
260
+
261
+
262
+ testIngredientWithEmoji:
263
+ source: |
264
+ Add some @🧂
265
+ result:
266
+ steps:
267
+ -
268
+ - type: text
269
+ value: "Add some "
270
+ - type: ingredient
271
+ name: "🧂"
272
+ quantity: "some"
273
+ units: ""
274
+ metadata: {}
275
+
276
+
277
+ testIngredientExplicitUnits:
278
+ source: |
279
+ @chilli{3%items}
280
+ result:
281
+ steps:
282
+ -
283
+ - type: ingredient
284
+ name: "chilli"
285
+ quantity: 3
286
+ units: "items"
287
+ metadata: {}
288
+
289
+
290
+ testIngredientExplicitUnitsWithSpaces:
291
+ source: |
292
+ @chilli{ 3 % items }
293
+ result:
294
+ steps:
295
+ -
296
+ - type: ingredient
297
+ name: "chilli"
298
+ quantity: 3
299
+ units: "items"
300
+ metadata: {}
301
+
302
+
303
+ testIngredientImplicitUnits:
304
+ source: |
305
+ @chilli{3}
306
+ result:
307
+ steps:
308
+ -
309
+ - type: ingredient
310
+ name: "chilli"
311
+ quantity: 3
312
+ units: ""
313
+ metadata: {}
314
+
315
+
316
+ testIngredientNoUnits:
317
+ source: |
318
+ @chilli
319
+ result:
320
+ steps:
321
+ -
322
+ - type: ingredient
323
+ name: "chilli"
324
+ quantity: "some"
325
+ units: ""
326
+ metadata: {}
327
+
328
+
329
+ testIngredientNoUnitsNotOnlyString:
330
+ source: |
331
+ @5peppers
332
+ result:
333
+ steps:
334
+ -
335
+ - type: ingredient
336
+ name: "5peppers"
337
+ quantity: "some"
338
+ units: ""
339
+ metadata: {}
340
+
341
+
342
+ testIngredientWithNumbers:
343
+ source: |
344
+ @tipo 00 flour{250%g}
345
+ result:
346
+ steps:
347
+ -
348
+ - type: ingredient
349
+ name: "tipo 00 flour"
350
+ quantity: 250
351
+ units: "g"
352
+ metadata: {}
353
+
354
+
355
+ testIngredientWithoutStopper:
356
+ source: |
357
+ @chilli cut into pieces
358
+ result:
359
+ steps:
360
+ -
361
+ - type: ingredient
362
+ name: "chilli"
363
+ quantity: "some"
364
+ units: ""
365
+ - type: text
366
+ value: " cut into pieces"
367
+ metadata: {}
368
+
369
+
370
+ testMetadata:
371
+ source: |
372
+ ---
373
+ sourced: babooshka
374
+ ---
375
+ result:
376
+ steps: []
377
+ metadata:
378
+ "sourced": babooshka
379
+
380
+
381
+ testMetadataBreak:
382
+ source: |
383
+ hello ---
384
+ sourced: babooshka
385
+ ---
386
+ result:
387
+ steps:
388
+ -
389
+ - type: text
390
+ value: "hello --- sourced: babooshka ---"
391
+ metadata: {}
392
+
393
+
394
+ testMetadataMultiwordKey:
395
+ source: |
396
+ ---
397
+ cooking time: 30 mins
398
+ ---
399
+ result:
400
+ steps: []
401
+ metadata:
402
+ "cooking time": 30 mins
403
+
404
+
405
+ testMetadataMultiwordKeyWithSpaces:
406
+ source: |
407
+ ---
408
+ cooking time :30 mins
409
+ ---
410
+ result:
411
+ steps: []
412
+ metadata:
413
+ "cooking time": 30 mins
414
+
415
+
416
+ testMultiLineDirections:
417
+ source: |
418
+ Add a bit of chilli
419
+
420
+ Add a bit of hummus
421
+ result:
422
+ steps:
423
+ -
424
+ - type: text
425
+ value: "Add a bit of chilli"
426
+ -
427
+ - type: text
428
+ value: "Add a bit of hummus"
429
+ metadata: {}
430
+
431
+
432
+ testMultipleLines:
433
+ source: |
434
+ ---
435
+ Prep Time: 15 minutes
436
+ Cook Time: 30 minutes
437
+ ---
438
+ result:
439
+ steps: []
440
+ metadata:
441
+ "Prep Time": 15 minutes
442
+ "Cook Time": 30 minutes
443
+
444
+
445
+ testMultiWordIngredient:
446
+ source: |
447
+ @hot chilli{3}
448
+ result:
449
+ steps:
450
+ -
451
+ - type: ingredient
452
+ name: "hot chilli"
453
+ quantity: 3
454
+ units: ""
455
+ metadata: {}
456
+
457
+
458
+ testMultiWordIngredientNoAmount:
459
+ source: |
460
+ @hot chilli{}
461
+ result:
462
+ steps:
463
+ -
464
+ - type: ingredient
465
+ name: "hot chilli"
466
+ quantity: "some"
467
+ units: ""
468
+ metadata: {}
469
+
470
+
471
+ testMutipleIngredientsWithoutStopper:
472
+ source: |
473
+ @chilli cut into pieces and @garlic
474
+ result:
475
+ steps:
476
+ -
477
+ - type: ingredient
478
+ name: "chilli"
479
+ quantity: "some"
480
+ units: ""
481
+ - type: text
482
+ value: " cut into pieces and "
483
+ - type: ingredient
484
+ name: "garlic"
485
+ quantity: "some"
486
+ units: ""
487
+ metadata: {}
488
+
489
+
490
+ testQuantityAsText:
491
+ source: |
492
+ @thyme{few%sprigs}
493
+ result:
494
+ steps:
495
+ -
496
+ - type: ingredient
497
+ name: "thyme"
498
+ quantity: few
499
+ units: "sprigs"
500
+ metadata: {}
501
+
502
+
503
+ testQuantityDigitalString:
504
+ source: |
505
+ @water{7 k }
506
+ result:
507
+ steps:
508
+ -
509
+ - type: ingredient
510
+ name: "water"
511
+ quantity: 7 k
512
+ units: ""
513
+ metadata: {}
514
+
515
+
516
+ testServings:
517
+ source: |
518
+ ---
519
+ servings: 1|2|3
520
+ ---
521
+ result:
522
+ steps: []
523
+ metadata:
524
+ "servings": 1|2|3
525
+
526
+
527
+ testSlashInText:
528
+ source: |
529
+ Preheat the oven to 200℃/Fan 180°C.
530
+ result:
531
+ steps:
532
+ -
533
+ - type: text
534
+ value: "Preheat the oven to 200℃/Fan 180°C."
535
+ metadata: {}
536
+
537
+
538
+ testTimerDecimal:
539
+ source: |
540
+ Fry for ~{1.5%minutes}
541
+ result:
542
+ steps:
543
+ -
544
+ - type: text
545
+ value: "Fry for "
546
+ - type: timer
547
+ quantity: 1.5
548
+ units: "minutes"
549
+ name: ""
550
+ metadata: {}
551
+
552
+
553
+ testTimerFractional:
554
+ source: |
555
+ Fry for ~{1/2%hour}
556
+ result:
557
+ steps:
558
+ -
559
+ - type: text
560
+ value: "Fry for "
561
+ - type: timer
562
+ quantity: 0.5
563
+ units: "hour"
564
+ name: ""
565
+ metadata: {}
566
+
567
+
568
+ testTimerInteger:
569
+ source: |
570
+ Fry for ~{10%minutes}
571
+ result:
572
+ steps:
573
+ -
574
+ - type: text
575
+ value: "Fry for "
576
+ - type: timer
577
+ quantity: 10
578
+ units: "minutes"
579
+ name: ""
580
+ metadata: {}
581
+
582
+
583
+ testTimerWithName:
584
+ source: |
585
+ Fry for ~potato{42%minutes}
586
+ result:
587
+ steps:
588
+ -
589
+ - type: text
590
+ value: "Fry for "
591
+ - type: timer
592
+ quantity: 42
593
+ units: "minutes"
594
+ name: "potato"
595
+ metadata: {}
596
+
597
+
598
+ testSingleWordTimer:
599
+ source: |
600
+ Let it ~rest after plating
601
+ result:
602
+ steps:
603
+ -
604
+ - type: text
605
+ value: "Let it "
606
+ - type: timer
607
+ quantity: ""
608
+ units: ""
609
+ name: "rest"
610
+ - type: text
611
+ value: " after plating"
612
+ metadata: {}
613
+
614
+
615
+ testSingleWordTimerWithPunctuation:
616
+ source: |
617
+ Let it ~rest, then serve
618
+ result:
619
+ steps:
620
+ -
621
+ - type: text
622
+ value: "Let it "
623
+ - type: timer
624
+ quantity: ""
625
+ units: ""
626
+ name: "rest"
627
+ - type: text
628
+ value: ", then serve"
629
+ metadata: {}
630
+
631
+
632
+ testSingleWordTimerWithUnicodePunctuation:
633
+ source: |
634
+ Let it ~rest⸫ then serve
635
+ result:
636
+ steps:
637
+ -
638
+ - type: text
639
+ value: "Let it "
640
+ - type: timer
641
+ quantity: ""
642
+ units: ""
643
+ name: "rest"
644
+ - type: text
645
+ value: "⸫ then serve"
646
+ metadata: {}
647
+
648
+ # NOTE: the space following `rest` is U+2009
649
+ testTimerWithUnicodeWhitespace:
650
+ source: |
651
+ Let it ~rest then serve
652
+ result:
653
+ steps:
654
+ -
655
+ - type: text
656
+ value: "Let it "
657
+ - type: timer
658
+ quantity: ""
659
+ units: ""
660
+ name: "rest"
661
+ - type: text
662
+ value: " then serve"
663
+ metadata: {}
664
+
665
+
666
+ testInvalidMultiWordTimer:
667
+ source: |
668
+ It is ~ {5}
669
+ result:
670
+ steps:
671
+ -
672
+ - type: text
673
+ value: "It is ~ {5}"
674
+ metadata: {}
675
+
676
+
677
+ testInvalidSingleWordTimer:
678
+ source: |
679
+ It is ~ 5
680
+ result:
681
+ steps:
682
+ -
683
+ - type: text
684
+ value: "It is ~ 5"
685
+ metadata: {}
686
+
687
+
688
+ testSingleWordIngredientWithPunctuation:
689
+ source: |
690
+ Add some @chilli, then serve
691
+ result:
692
+ steps:
693
+ -
694
+ - type: text
695
+ value: "Add some "
696
+ - type: ingredient
697
+ quantity: "some"
698
+ units: ""
699
+ name: "chilli"
700
+ - type: text
701
+ value: ", then serve"
702
+ metadata: {}
703
+
704
+
705
+ testSingleWordIngredientWithUnicodePunctuation:
706
+ source: |
707
+ Add @chilli⸫ then bake
708
+ result:
709
+ steps:
710
+ -
711
+ - type: text
712
+ value: "Add "
713
+ - type: ingredient
714
+ quantity: "some"
715
+ units: ""
716
+ name: "chilli"
717
+ - type: text
718
+ value: "⸫ then bake"
719
+ metadata: {}
720
+
721
+
722
+ # NOTE: the space following `chilli` is U+2009
723
+ testIngredientWithUnicodeWhitespace:
724
+ source: |
725
+ Add @chilli then bake
726
+ result:
727
+ steps:
728
+ -
729
+ - type: text
730
+ value: "Add "
731
+ - type: ingredient
732
+ quantity: "some"
733
+ units: ""
734
+ name: "chilli"
735
+ - type: text
736
+ value: " then bake"
737
+ metadata: {}
738
+
739
+
740
+ testInvalidMultiWordIngredient:
741
+ source: |
742
+ Message @ example{}
743
+ result:
744
+ steps:
745
+ -
746
+ - type: text
747
+ value: "Message @ example{}"
748
+ metadata: {}
749
+
750
+
751
+ testInvalidSingleWordIngredient:
752
+ source: |
753
+ Message me @ example
754
+ result:
755
+ steps:
756
+ -
757
+ - type: text
758
+ value: "Message me @ example"
759
+ metadata: {}
760
+
761
+
762
+ testSingleWordCookwareWithPunctuation:
763
+ source: |
764
+ Place in #pot, then boil
765
+ result:
766
+ steps:
767
+ -
768
+ - type: text
769
+ value: "Place in "
770
+ - type: cookware
771
+ quantity: 1
772
+ units: ""
773
+ name: "pot"
774
+ - type: text
775
+ value: ", then boil"
776
+ metadata: {}
777
+
778
+ testSingleWordCookwareWithUnicodePunctuation:
779
+ source: |
780
+ Place in #pot⸫ then boil
781
+ result:
782
+ steps:
783
+ -
784
+ - type: text
785
+ value: "Place in "
786
+ - type: cookware
787
+ quantity: 1
788
+ units: ""
789
+ name: "pot"
790
+ - type: text
791
+ value: "⸫ then boil"
792
+ metadata: {}
793
+
794
+
795
+ # NOTE: the space following `pot` is U+2009
796
+ testCookwareWithUnicodeWhitespace:
797
+ source: |
798
+ Add to #pot then boil
799
+ result:
800
+ steps:
801
+ -
802
+ - type: text
803
+ value: "Add to "
804
+ - type: cookware
805
+ quantity: 1
806
+ units: ""
807
+ name: "pot"
808
+ - type: text
809
+ value: " then boil"
810
+ metadata: {}
811
+
812
+
813
+ testInvalidMultiWordCookware:
814
+ source: |
815
+ Recipe # 10{}
816
+ result:
817
+ steps:
818
+ -
819
+ - type: text
820
+ value: "Recipe # 10{}"
821
+ metadata: {}
822
+
823
+
824
+ testInvalidSingleWordCookware:
825
+ source: |
826
+ Recipe # 5
827
+ result:
828
+ steps:
829
+ -
830
+ - type: text
831
+ value: "Recipe # 5"
832
+ metadata: {}
833
+
834
+ # NOTE: Unicode newlines may be impossible to test using YAML,
835
+ # given how the markup uses them for semantic reasoning.
836
+
837
+ # TODO add common syntax errors