--- /dev/null
+;;; org-chef-24kitchen.el --- org-chef 24kitchen fetching. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Calvin Beck, Folkert van der Beek
+
+;; Author: Folkert van der Beek <folkertvanderbeekNOSPAMgmail.com>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2019
+
+;; Copyright 2019 Folkert van der Beek
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from 24kitchen.nl.
+
+;;; Code:
+
+(require 'org-chef-utils)
+(require 'dom)
+
+(defun org-chef-24kitchen-extract-name (dom)
+ "Get the name of a recipe from an 24kitchen DOM."
+ (dom-text (dom-children (dom-by-class dom "^page-title$"))))
+
+(defun org-chef-24kitchen-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an 24kitchen DOM."
+ (org-chef-remove-empty-strings (mapcar #'(lambda (node) (string-trim (replace-regexp-in-string "[[:space:]\n]+" " " (dom-texts node)))) (dom-by-class dom "^recipe-component$"))))
+
+(defun org-chef-24kitchen-extract-servings (dom)
+ "Get the number of servings for a recipe from an 24kitchen DOM."
+ (string-trim (dom-text (dom-by-class dom "^portion-amount$"))))
+
+(defun org-chef-24kitchen-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from an 24kitchen DOM."
+ (string-trim (dom-text (dom-by-class dom "^cooking-time$"))))
+
+(defun org-chef-24kitchen-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from an 24kitchen DOM."
+ (string-trim (dom-text (dom-by-class dom "^total-time$"))))
+
+(defun org-chef-24kitchen-extract-directions (dom)
+ "Get the directions for a recipe from an 24kitchen DOM."
+ (org-chef-remove-empty-strings (mapcar #'(lambda (node) (string-trim (dom-text node))) (dom-children (cdr (dom-by-class dom "^recipe-body$"))))))
+
+(defun org-chef-24kitchen-fetch (url)
+ "Given an 24kitchen.nl URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ `((name . ,(org-chef-24kitchen-extract-name dom))
+ (ingredients . ,(org-chef-24kitchen-extract-ingredients dom))
+ (servings . ,(org-chef-24kitchen-extract-servings dom))
+ (prep-time . "")
+ (cook-time . ,(org-chef-24kitchen-extract-cook-time dom))
+ (ready-in . ,(org-chef-24kitchen-extract-ready-in dom))
+ (directions . ,(org-chef-24kitchen-extract-directions dom))
+ (source-url . ,url))))
+
+(provide 'org-chef-24kitchen)
+;;; org-chef-24kitchen.el ends here
--- /dev/null
+;;; org-chef-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from org-chef.el
+
+(autoload 'org-chef-insert-recipe "org-chef" "\
+Prompt for a recipe URL, and then insert the recipe at point.
+
+(fn URL)" t)
+(autoload 'org-chef-get-recipe-from-url "org-chef" "\
+Prompt for a recipe URL, and return the ‘org-mode’ string.")
+(register-definition-prefixes "org-chef" '("org-chef-"))
+
+\f
+;;; Generated autoloads from org-chef-24kitchen.el
+
+(register-definition-prefixes "org-chef-24kitchen" '("org-chef-24kitchen-"))
+
+\f
+;;; Generated autoloads from org-chef-basics-with-babish.el
+
+(register-definition-prefixes "org-chef-basics-with-babish" '("org-chef-basics-with-babish-"))
+
+\f
+;;; Generated autoloads from org-chef-bbc-food.el
+
+(register-definition-prefixes "org-chef-bbc-food" '("org-chef-bbc-food-"))
+
+\f
+;;; Generated autoloads from org-chef-bbc-good-food.el
+
+(register-definition-prefixes "org-chef-bbc-good-food" '("org-chef-bbc-good-food-"))
+
+\f
+;;; Generated autoloads from org-chef-binging-with-babish.el
+
+(register-definition-prefixes "org-chef-binging-with-babish" '("org-chef-b"))
+
+\f
+;;; Generated autoloads from org-chef-chef-koch.el
+
+(register-definition-prefixes "org-chef-chef-koch" '("org-chef-chef-koch-"))
+
+\f
+;;; Generated autoloads from org-chef-cultures-for-health.el
+
+(register-definition-prefixes "org-chef-cultures-for-health" '("org-chef-cultures-for-health-"))
+
+\f
+;;; Generated autoloads from org-chef-edit.el
+
+(autoload 'org-chef-edit-servings "org-chef-edit" "\
+Changes the serving count of an org-chef recipe entry to DESIRED, also modifying
+the quantities inside the ingredients list.
+
+(fn DESIRED)" t)
+(register-definition-prefixes "org-chef-edit" '("org-chef-edit--"))
+
+\f
+;;; Generated autoloads from org-chef-fine-cooking.el
+
+(register-definition-prefixes "org-chef-fine-cooking" '("org-chef-fine-cooking-"))
+
+\f
+;;; Generated autoloads from org-chef-genius-kitchen.el
+
+(register-definition-prefixes "org-chef-genius-kitchen" '("org-chef-genius-kitchen-"))
+
+\f
+;;; Generated autoloads from org-chef-jamie-oliver.el
+
+(register-definition-prefixes "org-chef-jamie-oliver" '("org-chef-jamie-oliver-"))
+
+\f
+;;; Generated autoloads from org-chef-json-ld.el
+
+(register-definition-prefixes "org-chef-json-ld" '("org-chef-json-ld-"))
+
+\f
+;;; Generated autoloads from org-chef-kotikokki.el
+
+(register-definition-prefixes "org-chef-kotikokki" '("org-chef-kotikokki-"))
+
+\f
+;;; Generated autoloads from org-chef-marmiton.el
+
+(register-definition-prefixes "org-chef-marmiton" '("org-chef-marmiton-"))
+
+\f
+;;; Generated autoloads from org-chef-martha-stewart.el
+
+(register-definition-prefixes "org-chef-martha-stewart" '("org-chef-martha-stewart-"))
+
+\f
+;;; Generated autoloads from org-chef-nytimes.el
+
+(register-definition-prefixes "org-chef-nytimes" '("org-chef-nytimes-"))
+
+\f
+;;; Generated autoloads from org-chef-recipetin-eats.el
+
+(register-definition-prefixes "org-chef-recipetin-eats" '("org-chef-recipetin-eats-"))
+
+\f
+;;; Generated autoloads from org-chef-reluctant-gourmet.el
+
+(register-definition-prefixes "org-chef-reluctant-gourmet" '("org-chef-reluctant-gourmet-"))
+
+\f
+;;; Generated autoloads from org-chef-saveur.el
+
+(register-definition-prefixes "org-chef-saveur" '("org-chef-saveur-"))
+
+\f
+;;; Generated autoloads from org-chef-serious-eats.el
+
+(register-definition-prefixes "org-chef-serious-eats" '("org-chef-serious-eats-"))
+
+\f
+;;; Generated autoloads from org-chef-simply-recipes.el
+
+(register-definition-prefixes "org-chef-simply-recipes" '("org-chef-simply-recipes-"))
+
+\f
+;;; Generated autoloads from org-chef-taste.el
+
+(register-definition-prefixes "org-chef-taste" '("org-chef-taste-"))
+
+\f
+;;; Generated autoloads from org-chef-utils.el
+
+(register-definition-prefixes "org-chef-utils" '("org-chef-"))
+
+\f
+;;; Generated autoloads from org-chef-wordpress.el
+
+(register-definition-prefixes "org-chef-wordpress" '("org-chef-"))
+
+\f
+;;; Generated autoloads from org-chef-xiachufang.el
+
+(register-definition-prefixes "org-chef-xiachufang" '("org-chef-xiachufang-"))
+\f
+;;; End of scraped data
+
+(provide 'org-chef-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; org-chef-autoloads.el ends here
--- /dev/null
+;;; org-chef-basics-with-babish.el --- Functions for fetching recipes from basicswithbabish.co. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+;; Copyright (C) 2021 Alexander Huntley
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from basicswithbabish.co
+;;
+;; More manual than most parsers, since the site does not have a
+;; standard structure, and each page contains multiple recipes.
+;; Hence, we ask the user to choose which recipes are wanted,
+;; and we concatenate them together.
+;;
+;; Reuses functions from org-chef-binging-with-babish
+(require 'org-chef-utils)
+(require 'org-chef-binging-with-babish)
+(require 'dom)
+
+
+(defun org-chef-basics-with-babish-extract-name (dom)
+ "Get the name of a recipe from a DOM."
+ (capitalize (dom-text (dom-by-class dom "BlogItem-title"))))
+
+
+(defun org-chef-basics-with-babish-extract-ingredients (dom)
+ "Get the ingredients for a recipe from a DOM. Prompts user to select
+desired <ul>s."
+ (org-chef-babish-choose-from-lists
+ (car (dom-by-tag dom 'main)) 'ul
+ "Select ingredient lists (comma-separated): "))
+
+
+(defun org-chef-basics-with-babish-extract-directions (dom)
+ "Get the directions for a recipe from a DOM. Prompts user to select
+desired <ol>s."
+ (org-chef-babish-choose-from-lists
+ (car (dom-by-tag dom 'main)) 'ol
+ "Select instruction lists (comma-separated): "))
+
+
+(defun org-chef-basics-with-babish-from-dom (dom)
+ "Given a basicswithbabish.co DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ `((ingredients . ,(org-chef-basics-with-babish-extract-ingredients dom))
+ (name . ,(org-chef-basics-with-babish-extract-name dom))
+ (servings . nil)
+ (prep-time . nil)
+ (cook-time . nil)
+ (ready-in . nil)
+ (directions . ,(org-chef-basics-with-babish-extract-directions dom))))
+
+
+(defun org-chef-basics-with-babish-fetch (url)
+ "Given a basicswithbabish.co URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-basics-with-babish-from-dom dom))))
+
+
+(provide 'org-chef-basics-with-babish)
+;;; org-chef-basics-with-babish.el ends here
--- /dev/null
+
+;;; org-chef-bbc-food.el --- Functions for fetching recipes from .de. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from bbc.co.uk/food/
+(require 'org-chef-utils)
+(require 'dom)
+
+(defun org-chef-bbc-food-extract-name (dom)
+ "Get the name of a recipe from a DOM."
+ (dom-text (dom-by-class dom "content-title__text") ))
+
+
+(defun org-chef-bbc-food-extract-ingredients (dom)
+ "Get the ingredients for a recipe from a DOM."
+ (mapcar 'dom-texts (dom-by-tag (dom-by-class dom "recipe-ingredient-list") 'li)))
+
+
+(defun org-chef-bbc-food-extract-servings (dom)
+ "Get the number of servings for a recipe from a DOM."
+ (dom-text (dom-by-class dom "recipe-metadata__serving")))
+
+(defun org-chef-bbc-food-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from a DOM."
+ (dom-text (dom-by-class dom "recipe-metadata__prep-time")))
+
+(defun org-chef-bbc-food-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from a DOM."
+(dom-text (dom-by-class dom "recipe-metadata__cook-time")))
+
+(defun org-chef-bbc-food-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from a DOM."
+ (dom-text (dom-by-class dom "recipe-metadata__prep-time")))
+
+(defun org-chef-bbc-food-extract-directions (dom)
+ "Get the directions for a recipe from a DOM."
+ (mapcar 'dom-texts (dom-by-tag (dom-by-class dom "recipe-method__list") 'li)))
+
+
+(defun org-chef-bbc-food-from-dom (dom)
+ "Given a .de DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ (let* ((prep-time (org-chef-bbc-food-extract-prep-time dom))
+ (cook-time (org-chef-bbc-food-extract-cook-time dom))
+ (total-time (org-chef-bbc-food-extract-ready-in dom)))
+
+ `((ingredients . ,(org-chef-bbc-food-extract-ingredients dom))
+ (name . ,(org-chef-bbc-food-extract-name dom))
+ (servings . ,(org-chef-bbc-food-extract-servings dom))
+ (prep-time . ,prep-time)
+ (cook-time . ,cook-time)
+ (ready-in . ,total-time)
+ (directions . ,(org-chef-bbc-food-extract-directions dom)))))
+
+
+(defun org-chef-bbc-food-fetch (url)
+ "Given a .de URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-bbc-food-from-dom dom))))
+
+
+(provide 'org-chef-bbc-food)
+;;; org-chef-bbc-food.el ends here
--- /dev/null
+
+;;; org-chef-bbc-good-food.el --- Functions for fetching recipes from bbcgoodfood.com -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from www.bbcgoodfood.com
+(require 'org-chef-utils)
+(require 'org-chef-json-ld)
+
+(defun org-chef-bbc-good-food-clean-direction (direction)
+ "Removes <p> tags from a single direction string"
+ (replace-regexp-in-string "</?p>" "" direction))
+
+(defun org-chef-bbc-good-food-fetch (url)
+ "Given a bbcgoodfood.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let* ((result (org-chef-json-ld-fetch url))
+ (directions (assoc 'directions result))
+ (cleaned (mapcar #'org-chef-bbc-good-food-clean-direction (cdr directions))))
+ (setf (cdr directions) cleaned)
+ result))
+
+
+(provide 'org-chef-bbc-good-food)
+;;; org-chef-bbc-good-food.el ends here
--- /dev/null
+;;; org-chef-binging-with-babish.el --- Functions for fetching recipes from bingingwithbabish.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+;; Copyright (C) 2021 Alexander Huntley
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.comm/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from bingingwithbabish.com
+;; More manual than most parsers, since the site does not have a
+;; standard structure, and each page contains multiple recipes.
+;; Hence, we ask the user to choose which recipes are wanted,
+;; and we concatenate them together.
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-babish-list-title (dom list)
+ "Gets the title of a <ul> or <ol> by finding the preceding <p> tag"
+ (let* ((parent (dom-parent dom list))
+ (siblings (dom-children parent))
+ (position (position list siblings))
+ (previous (nth (1- position) siblings)))
+ (unless (member (dom-tag previous) '("ul" "ol" "li"))
+ (replace-regexp-in-string ":\\|^[\n ]*\\|[\n ]*$\\|\n" ""
+ (dom-texts previous)))))
+
+
+(defun org-chef-babish-choose-from-lists (dom tag prompt)
+ "Prompts the user to choose between all the lists of a given tag
+ type ('ul or 'ol) in the DOM, and then concatenates their items."
+ (let* ((ols (dom-by-tag dom tag))
+ (alist (mapcar
+ (lambda (ol) (cons (org-chef-babish-list-title dom ol) ol))
+ ols))
+ (chosen (completing-read-multiple prompt alist)))
+ (mapcan (lambda (ol) (mapcar #'dom-texts (dom-children ol)))
+ (mapcar (lambda (s) (cdr (assoc s alist))) chosen))))
+
+
+(defun org-chef-binging-with-babish-extract-name (dom)
+ "Get the name of a recipe from a DOM."
+ (replace-regexp-in-string
+ "^[\n\r\t ]*\\|[\n\r\t ]*$" ""
+ (dom-text (dom-by-class dom "^page-title$"))))
+
+
+(defun org-chef-binging-with-babish-extract-ingredients (dom)
+ "Get the ingredients for a recipe from a DOM. Prompts user to select
+desired <ul>s."
+ (org-chef-babish-choose-from-lists
+ (car (dom-by-class dom "entry-content")) 'ul
+ "Select ingredient lists (comma-separated): "))
+
+
+(defun org-chef-binging-with-babish-extract-directions (dom)
+ "Get the directions for a recipe from a DOM. Prompts user to select
+desired <ol>s."
+ (org-chef-babish-choose-from-lists
+ (car (dom-by-class dom "entry-content")) 'ol
+ "Select instruction lists (comma-separated): "))
+
+
+(defun org-chef-binging-with-babish-from-dom (dom)
+ "Given a bingingwithbabish.com DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ `((ingredients . ,(org-chef-binging-with-babish-extract-ingredients dom))
+ (name . ,(org-chef-binging-with-babish-extract-name dom))
+ (servings . nil)
+ (prep-time . nil)
+ (cook-time . nil)
+ (ready-in . nil)
+ (directions . ,(org-chef-binging-with-babish-extract-directions dom))))
+
+
+(defun org-chef-binging-with-babish-fetch (url)
+ "Given a bingingwithbabish.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-binging-with-babish-from-dom dom))))
+
+
+(provide 'org-chef-binging-with-babish)
+;;; org-chef-binging-with-babish.el ends here
--- /dev/null
+;;; org-chef-chef-koch.el --- Functions for fetching recipes from chefkoch.de. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from chefkoch.de.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-chef-koch-extract-name (dom)
+ "Get the name of a recipe from a chefkoch DOM."
+ (dom-text (dom-by-tag (dom-by-class dom "recipe-header") 'h1)))
+
+
+(defun org-chef-chef-koch-ingredient-to-string (dom)
+ (let ((amount (replace-regexp-in-string "[ \\t\\n\\r]+" " " (string-trim (dom-texts (car (dom-by-tag dom 'td))))))
+ (name (string-trim (dom-texts (cadr (dom-by-tag dom 'td))))))
+ (format "%s %s" amount name)))
+
+
+(defun org-chef-chef-koch-extract-ingredients (dom)
+ "Get the ingredients for a recipe from a chefkoch DOM."
+ (mapcar #'org-chef-chef-koch-ingredient-to-string (dom-by-tag (dom-by-class dom "ingredients") 'tr)))
+
+
+(defun org-chef-chef-koch-extract-servings (dom)
+ "Get the number of servings for a recipe from a chefkoch DOM."
+ (dom-attr (dom-elements dom 'name "^portionen$") 'value))
+
+
+(defun org-chef-chef-koch-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from a chefkoch DOM."
+ (let ((str (replace-regexp-in-string "\n" " " (dom-texts (dom-by-id dom "^preparation-info$")))))
+ (string-match "Arbeitszeit:[[:space:]]+ca. \\(?1:[^\.]*\\)\." str)
+ (match-string 1 str)))
+
+
+(defun org-chef-chef-koch-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from a chefkoch DOM."
+ (let ((str (replace-regexp-in-string "\n" " " (dom-texts (dom-by-id dom "^preparation-info$")))))
+ (string-match "Koch-/Backzeit:[[:space:]]+ca. \\(?1:[^\.]*\\)\." str)
+ (match-string 1 str)))
+
+
+(defun org-chef-chef-koch-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from a chefkoch DOM."
+ "")
+
+
+(defun org-chef-chef-koch-extract-directions (dom)
+ "Get the directions for a recipe from a chefkoch DOM."
+ (with-temp-buffer
+ (insert (string-trim (dom-text (car (dom-by-tag (nth 3 (dom-by-tag dom 'article)) 'div)))))
+ (delete-trailing-whitespace)
+ (split-string (buffer-string) "\n")))
+
+
+(defun org-chef-chef-koch-from-dom (dom)
+ "Given a chefkoch.de DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ (let* ((prep-time (org-chef-chef-koch-extract-prep-time dom))
+ (cook-time (org-chef-chef-koch-extract-cook-time dom))
+ (total-time (org-chef-chef-koch-extract-ready-in dom)))
+
+ `((ingredients . ,(org-chef-chef-koch-extract-ingredients dom))
+ (name . ,(org-chef-chef-koch-extract-name dom))
+ (servings . ,(org-chef-chef-koch-extract-servings dom))
+ (prep-time . ,prep-time)
+ (cook-time . ,cook-time)
+ (ready-in . ,total-time)
+ (directions . ,(org-chef-chef-koch-extract-directions dom)))))
+
+
+(defun org-chef-chef-koch-fetch (url)
+ "Given a chefkoch.de URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-chef-koch-from-dom dom))))
+
+
+(provide 'org-chef-chef-koch)
+;;; org-chef-chef-koch.el ends here
--- /dev/null
+;;; org-chef-cultures-for-health.el --- Functions for fetching recipes from culturesforhealth.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from culturesforhealth.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-cultures-for-health-extract-name (dom)
+ "Get the name of a recipe from an culturesforhealth DOM."
+ (dom-text (car (dom-elements dom 'itemprop "^name$"))))
+
+
+(defun org-chef-cultures-for-health-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an culturesforhealth DOM."
+ (mapcar 'dom-texts (dom-elements dom 'itemprop "^ingredients$")))
+
+
+(defun org-chef-cultures-for-health-extract-directions (dom)
+ "Get the directions for a recipe from an culturesforhealth DOM."
+ (mapcar #'dom-texts (org-chef-dom-children (cadr (org-chef-dom-children (car (dom-elements dom 'itemprop "^recipeInstructions$")))))))
+
+
+(defun org-chef-cultures-for-health-from-dom (dom)
+ "Given a culturesforhealth.com DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ `((ingredients . ,(org-chef-cultures-for-health-extract-ingredients dom))
+ (name . ,(org-chef-cultures-for-health-extract-name dom)
+ )
+ (servings . nil)
+ (prep-time . nil)
+ (cook-time . nil)
+ (ready-in . nil)
+ (directions . ,(org-chef-cultures-for-health-extract-directions dom))))
+
+
+(defun org-chef-cultures-for-health-fetch (url)
+ "Given a culturesforhealth.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-cultures-for-health-from-dom dom))))
+
+
+(provide 'org-chef-cultures-for-health)
+;;; org-chef-cultures-for-health.el ends here
--- /dev/null
+;;; org-chef-edit.el --- Cookbook and recipe management with org-mode. -*- lexical-binding: t; -*-
+
+;; Copyright 2021 Alexander Huntley
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Provides commands to modify the number of servings in a recipe.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'org-chef-utils)
+
+(defun org-chef-edit--do-replace (multiplier)
+ "Replaces all numbers (in a variety of formats) in the region,
+multiplying them all by MULTIPLIER"
+ (save-excursion
+ (while (re-search-forward
+ "\\([0-9]+\\)?\\([¼½¾⅐⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜⅝⅞]\\)\\|\\([0-9]+\\)/\\([0-9]+\\)\\|\\([0-9]+\\)" (mark) t)
+ (cond
+ ;; Unicode vulgar/mixed fractions
+ ((match-string 2)
+ (let* ((char (elt (match-string 2) 0))
+ (frac (get-char-code-property char 'numeric-value))
+ (int (if (match-string 1) (string-to-number (match-string 1)) 0))
+ (original (+ int frac)))
+ (replace-match (format "%.3g" (* multiplier original)))))
+ ;; ASCII fractions
+ ((match-string 3)
+ (let ((original (/ (float (string-to-number (match-string 3)))
+ (float (string-to-number (match-string 4))))))
+ (replace-match (format "%.3g" (* multiplier original)))))
+ ;; simple decimals
+ ((match-string 5)
+ (replace-match
+ (format "%.3g" (* multiplier (string-to-number
+ (match-string 5))))))))))
+
+
+(defun org-chef-edit--parse-servings (string)
+ "Finds all integers in STRING, and computes their mean"
+ (let ((ints (mapcar #'string-to-number (org-chef-regexp-matches "[0-9]+" string))))
+ (/ (cl-reduce #'+ ints) (length ints))))
+
+
+;;;###autoload
+(defun org-chef-edit-servings (desired)
+ "Changes the serving count of an org-chef recipe entry to DESIRED, also modifying
+the quantities inside the ingredients list."
+ (interactive "*sChange servings to: ")
+ (save-excursion
+ (condition-case nil
+ (while (not (org-entry-get nil "servings"))
+ (outline-up-heading 1))
+ (error (error "Could not find recipe with \"servings\" property")))
+ (outline-show-subtree)
+ (save-excursion
+ (let* ((servings (org-chef-edit--parse-servings
+ (org-entry-get nil "servings")))
+ (desired (if (numberp desired) desired
+ (org-chef-edit--parse-servings desired)))
+ (multiplier (/ (float desired) (float servings))))
+ (outline-next-heading)
+ (outline-mark-subtree)
+ (next-line)
+ (org-chef-edit--do-replace multiplier)))
+ (org-entry-put nil "servings" (format "%s" desired))))
+
+
+(provide 'org-chef-edit)
+;;; org-chef-edit.el ends here
--- /dev/null
+;;; org-chef-fine-cooking.el --- Functions for fetching recipes from finecooking.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Calvin Beck, Erik Hetzner
+
+;; Author: Erik Hetzner <eghNOSPAMe6h.org>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2019
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from finecooking.com.
+
+;;; Code:
+
+(require 'org-chef-utils)
+(require 'dom)
+
+(defun org-chef-fine-cooking-extract-name (dom)
+ "Get the name of a recipe from an finecooking DOM."
+ (dom-text (dom-by-class dom "^recipe__title")))
+
+(defun org-chef-fine-cooking-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an finecooking DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-texts n)))
+ (org-chef-dom-children (dom-child-by-tag (dom-by-class dom "^recipe__ingredients$") 'ul))))
+
+(defun org-chef-fine-cooking-extract-servings (dom)
+ "Get the number of servings for a recipe from an finecooking DOM."
+ (dom-text (dom-child-by-tag (dom-by-class dom "^recipe__yield$") 'p)))
+
+(defun org-chef-fine-cooking-extract-directions (dom)
+ "Get the directions for a recipe from an finecooking DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-texts n)))
+ (org-chef-dom-children (dom-child-by-tag (dom-by-class dom "^recipe__preparation$") 'ul))))
+
+(defun org-chef-fine-cooking-fetch (url)
+ "Given a finecooking.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ `((name . ,(org-chef-fine-cooking-extract-name dom))
+ (ingredients . ,(org-chef-fine-cooking-extract-ingredients dom))
+ (servings . ,(org-chef-fine-cooking-extract-servings dom))
+ (prep-time . nil)
+ (cook-time . nil)
+ (ready-in . nil)
+ (directions . ,(org-chef-fine-cooking-extract-directions dom))
+ (source-url . ,url))))
+
+(provide 'org-chef-fine-cooking)
+;;; org-chef-fine-cooking.el ends here
--- /dev/null
+;;; org-chef-genius-kitchen.el --- Functions for fetching recipes from geniuskitchen.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from geniuskitchen.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-genius-kitchen-extract-name (dom)
+ "Get the name of a recipe from an geniuskitchen DOM."
+ (dom-texts (dom-by-class dom "^recipe-title$")))
+
+
+(defun org-chef-genius-kitchen-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an geniuskitchen DOM."
+ (mapcar #'dom-texts (dom-by-class dom "^recipe-ingredients__ingredient$")))
+
+
+(defun org-chef-genius-kitchen-extract-servings (dom)
+ "Get the number of servings for a recipe from an geniuskitchen DOM."
+ (dom-texts (dom-by-class (car (dom-by-class dom "recipe-facts__yield")) "theme-color")))
+
+
+(defun org-chef-genius-kitchen-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from an geniuskitchen DOM."
+ (dom-texts (cddddr (car (dom-by-class dom "recipe-facts__time")))))
+
+
+(defun org-chef-genius-kitchen-extract-directions (dom)
+ "Get the directions for a recipe from an geniuskitchen DOM."
+ (let ((directions-list (dom-by-tag (car (dom-by-class dom "recipe-directions__step")) 'li)))
+ (org-chef-remove-empty-strings
+ (mapcar #'(lambda (n) (string-trim (dom-text n))) directions-list))))
+
+
+(defun org-chef-genius-kitchen-fetch (url)
+ "Given a geniuskitchen.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ `((name . ,(org-chef-genius-kitchen-extract-name dom))
+ (ingredients . ,(org-chef-genius-kitchen-extract-ingredients dom))
+ (servings . ,(org-chef-genius-kitchen-extract-servings dom))
+ (prep-time . nil)
+ (cook-time . nil)
+ (ready-in . ,(org-chef-genius-kitchen-extract-ready-in dom))
+ (directions . ,(org-chef-genius-kitchen-extract-directions dom))
+ (source-url . ,url))))
+
+
+(provide 'org-chef-genius-kitchen)
+;;; org-chef-genius-kitchen.el ends here
--- /dev/null
+
+;;; org-chef-jamie-oliver.el --- Functions for fetching recipes from jamieoliver.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from bbc.co.uk/food/
+(require 'org-chef-utils)
+(require 'dom)
+(require 'org-chef-json-ld)
+
+
+(defun org-chef-jamie-oliver-clean-direction (xml)
+ "Parses dom in direction string, returning a list of directions"
+ (let ((direction-dom (org-chef-string-to-dom xml)))
+ (if direction-dom
+ (dom-strings direction-dom)
+ (split-string xml "[\f
+\r\v]+"))))
+
+
+(defun org-chef-jamie-oliver-fetch (url)
+ "Given a jamieoliver.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let* ((result (org-chef-json-ld-fetch url))
+ (directions (assoc 'directions result))
+ (cleaned (mapcan #'org-chef-jamie-oliver-clean-direction (cdr directions))))
+ (setf (cdr directions) cleaned)
+ result))
+
+
+(provide 'org-chef-jamie-oliver)
+;;; org-chef-jamie-oliver.el ends here
--- /dev/null
+;;; org-chef-json-ld.el --- org-chef json-ld fetching. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2020 Erik Hetzner
+
+;; Author: Erik Hetzner <eghNOSPAMe6h.org>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2020
+
+;; Copyright (C) 2020 Erik Hetzner
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from sites supporting json-ld.
+;; https://developers.google.com/search/docs/data-types/recipe#structured-data-type-definitions
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+(require 'json)
+(require 'cl-seq)
+
+(defcustom org-chef-json-ld-debug nil
+ "Debug JSON-LD parsing. Surfaces errors in JSON parsing."
+ :type 'boolean)
+
+(defun org-chef-json-ld-extract-directions (nodes)
+ "Get the directions for a recipe from a list of json NODES."
+ (cond ((stringp nodes)
+ (list nodes))
+ ((sequencep nodes)
+ (mapcar (lambda (n)
+ (cond ((stringp n) n)
+ (t (cdr (assq 'text n)))))
+ nodes))
+ (t (list "Unknown"))))
+
+(defun org-chef-json-ld-pluralize (count name)
+ "Pluralize the NAME based on the value of COUNT."
+ (cond ((eq count 0)
+ nil)
+ ((eq count 1)
+ (format "%d %s" count name))
+ (t
+ (format "%d %ss" count name))))
+
+(defun org-chef-json-ld-parse-duration (duration)
+ "Parse a ISO 8601 DURATION and return an English language string, or Unknown if it could not be parsed."
+ (if (or (null duration)
+ (not (string-match "^P0?D?T\\([0-9]+H\\)?\\([0-9]+M\\)?$" duration)))
+ "Unknown"
+ (let* ((hours-raw (match-string 1 duration))
+ (minutes-raw (match-string 2 duration))
+ (hours (and hours-raw (string-to-number (substring hours-raw 0 -1))))
+ (minutes (and minutes-raw (string-to-number (substring minutes-raw 0 -1))))
+ (hours-string (and hours (org-chef-json-ld-pluralize hours "hour")))
+ (minutes-string (and minutes (org-chef-json-ld-pluralize minutes "minute"))))
+ (string-join (cl-delete-if #'null (list hours-string minutes-string)) ", "))))
+
+(defun org-chef-json-ld-clean-json (json)
+ "Clean JSON string of common errors."
+ (let* ((cleaned-1 (replace-regexp-in-string "\n" " " json))
+ (cleaned-2 (replace-regexp-in-string "\r" " " cleaned-1))
+ (cleaned-3 (replace-regexp-in-string "\t" " " cleaned-2)))
+ cleaned-3))
+
+(defun org-chef-json-ld-extract-recipe (json)
+ "Find the Recipe LD in JSON and return the hash. Return nil if not found."
+ (cond ((and (json-alist-p json)
+ (let ((type (cdr (assq '@type json))))
+ (or (and (stringp type)
+ (string= type "Recipe"))
+ (and (sequencep type)
+ (seq-contains type "Recipe")))))
+ json)
+ ((and (json-alist-p json)
+ (assq '@graph json)
+ (sequencep (cdr (assq '@graph json))))
+ (cl-some #'org-chef-json-ld-extract-recipe (cdr (assq '@graph json))))
+ ((and (not (json-alist-p json))
+ (sequencep json))
+ (cl-some #'org-chef-json-ld-extract-recipe json))
+ (t nil)))
+
+(defun org-chef-json-ld-safe-read-from-string (str)
+ "Safely parse STR as json.
+
+Like `json-read-from-string', but catch errors and return nil if
+`org-chef-json-ld-debug' is not t."
+ (if org-chef-json-ld-debug
+ (json-read-from-string str)
+ (ignore-errors
+ (json-read-from-string str))))
+
+(defun org-chef-json-ld-extract-json-ld (dom)
+ "Extract a list of the json-ld elements in DOM."
+ (let* ((json-lds (dom-elements dom 'type "^application/ld\\+json$"))
+ (json-lds-raw (mapcar #'dom-text json-lds))
+ (json-lds-cleaned (mapcar #'org-chef-json-ld-clean-json json-lds-raw))
+ (json-lds (cl-delete-if #'null (mapcar #'org-chef-json-ld-safe-read-from-string json-lds-cleaned))))
+ json-lds))
+
+(defun org-chef-json-ld-fetch (url)
+ "Given an URL, retrieve the recipe information.
+
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let* ((dom (org-chef-url-retrieve-dom url))
+ (json-lds (org-chef-json-ld-extract-json-ld dom))
+ (recipe (org-chef-json-ld-extract-recipe json-lds)))
+ (if (null recipe)
+ nil
+ `((name . ,(cdr (assq 'name recipe)))
+ (ingredients . ,(cdr (assq 'recipeIngredient recipe)))
+ (servings . ,(format "%s" (cdr (assq 'recipeYield recipe))))
+ (ready-in . ,(org-chef-json-ld-parse-duration (cdr (assq 'totalTime recipe))))
+ (prep-time . ,(org-chef-json-ld-parse-duration (cdr (assq 'prepTime recipe))))
+ (cook-time . ,(org-chef-json-ld-parse-duration (cdr (assq 'cookTime recipe))))
+ (directions . ,(org-chef-json-ld-extract-directions (cdr (assq 'recipeInstructions recipe))))
+ (source-url . ,url)))))
+
+(provide 'org-chef-json-ld)
--- /dev/null
+
+;;; org-chef-bbc-good-food.el --- Functions for fetching recipes from bbcgoodfood.com -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from www.bbcgoodfood.com
+(require 'org-chef-utils)
+(require 'org-chef-json-ld)
+
+(defun org-chef-kotikokki-clean-direction (direction)
+ "Removes <p> tags from a single direction string"
+ (replace-regexp-in-string "</?p>" "" direction))
+
+(defun org-chef-kotikokki-fetch (url)
+ "Given a kotikokki.net URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- ready-in
+- directions
+- source-url"
+ (let* ((result (org-chef-json-ld-fetch url))
+ (directions (assoc 'directions result))
+ (cleaned (mapcar #'org-chef-kotikokki-clean-direction (cdr directions))))
+ (setf (cdr directions) cleaned)
+ result))
+
+
+(provide 'org-chef-kotikokki)
+;;; org-chef-bbc-good-food.el ends here
--- /dev/null
+;;; org-chef-marmiton.el --- Functions for fetching recipes from marmiton.org. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck/Sébastien Le Maguer
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>/Sébastien Le Maguer <seb.lemaguerNOSPAMgmail.com>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck/Sébastien Le Maguer
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from marmiton.org.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-marmiton-sanitize (str)
+ "Sanitize the STR by removing beginning/trailing spaces extracted from a marmiton dom."
+ (replace-regexp-in-string "^[ \r\n]*\\(.*[^ \r\n]\\)[ \r\n]*$" "\\1" str))
+
+(defun org-chef-marmiton-extract-dom-text (dom)
+ "Embedded the operation santize of dom-text from a marmiton DOM."
+ (org-chef-marmiton-sanitize (dom-texts dom)))
+
+(defun org-chef-marmiton-extract-name (dom)
+ "Get the name of a recipe from a marmiton DOM."
+ (dom-text (car (dom-elements dom 'class "^main-title $"))))
+
+(defun org-chef-marmiton-extract-current-ingredient (dom)
+ "Extract element for the current ingredient from a marmiton DOM."
+ (concat
+ (org-chef-marmiton-extract-dom-text (dom-elements dom 'class "^recipe-ingredient-qt$"))
+ " "
+ (org-chef-marmiton-extract-dom-text (dom-elements dom 'class "^ingredient$"))))
+
+(defun org-chef-marmiton-extract-ingredients (dom)
+ "Get the ingredients for a recipe from a marmiton DOM."
+ (mapcar 'org-chef-marmiton-extract-current-ingredient (dom-elements dom 'class "^recipe-ingredients__list__item$")))
+
+(defun org-chef-marmiton-extract-directions (dom)
+ "Get the directions for a recipe from a marmiton DOM."
+
+ (mapcar (lambda (x) (org-chef-marmiton-extract-dom-text (cddr x)))
+ (dom-elements dom 'class "^recipe-preparation__list__item$")))
+
+(defun org-chef-marmiton-extract-prep-time (dom)
+ "Get the preparation time for a recipe from a marmiton DOM."
+ (org-chef-marmiton-extract-dom-text
+ (dom-elements (dom-elements dom 'class "recipe-infos__timmings__preparation")
+ 'class "^recipe-infos__timmings__value$")))
+
+(defun org-chef-marmiton-extract-cook-time (dom)
+ "Extract the cooking time for a recipe from a marmiton DOM."
+ (org-chef-marmiton-extract-dom-text
+ (dom-elements (dom-elements dom 'class "recipe-infos__timmings__cooking")
+ 'class "^recipe-infos__timmings__value$")))
+
+
+(defun org-chef-marmiton-extract-servings (dom)
+ (dom-text (car (dom-elements dom 'class "^title-2 recipe-infos__quantity__value$"))))
+
+
+
+(defun org-chef-marmiton-from-dom (dom)
+ "Given a marmiton.org DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ `((ingredients . ,(org-chef-marmiton-extract-ingredients dom))
+ (name . ,(org-chef-marmiton-extract-name dom))
+ (servings . ,(org-chef-marmiton-extract-servings dom))
+ (prep-time . ,(org-chef-marmiton-extract-prep-time dom))
+ (cook-time . ,(org-chef-marmiton-extract-cook-time dom))
+ (ready-in . nil)
+ (directions . ,(org-chef-marmiton-extract-directions dom))))
+
+
+(defun org-chef-marmiton-fetch (url)
+ "Given a marmiton.org URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-marmiton-from-dom dom))))
+
+(provide 'org-chef-marmiton)
+;;; org-chef-marmiton.el ends here
--- /dev/null
+;;; org-chef-martha-stewart.el --- org-chef marthastewart fetching. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbes@ualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from marthastewart.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-martha-stewart-extract-name (dom)
+ "Get the name of a recipe from an marthastewart DOM."
+ (string-trim (dom-text (car (dom-by-class dom "^page-title$")))))
+
+
+(defun org-chef-martha-stewart-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an marthastewart DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-text n)))
+ (dom-by-class dom "^component-text$")))
+
+
+(defun org-chef-martha-stewart-extract-servings (dom)
+ "Get the number of servings for a recipe from an marthastewart DOM."
+ (string-trim (dom-text (car (dom-by-class dom "servings-data$")))))
+
+
+(defun org-chef-martha-stewart-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from an marthastewart DOM."
+ (string-trim (dom-text (car (dom-by-class dom "prep-time-data$")))))
+
+
+(defun org-chef-martha-stewart-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from an marthastewart DOM."
+ nil)
+
+
+(defun org-chef-martha-stewart-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from an marthastewart DOM."
+ (string-trim (dom-text (car (dom-by-class dom "total-time-data$")))))
+
+
+(defun org-chef-martha-stewart-extract-directions (dom)
+ "Get the directions for a recipe from an marthastewart DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-texts (dom-children n))))
+ (dom-by-class dom "^directions-item-text$")))
+
+
+(defun org-chef-martha-stewart-fetch (url)
+ "Given an marthastewart.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ `((name . ,(org-chef-martha-stewart-extract-name dom))
+ (ingredients . ,(org-chef-martha-stewart-extract-ingredients dom))
+ (servings . ,(org-chef-martha-stewart-extract-servings dom))
+ (prep-time . ,(org-chef-martha-stewart-extract-prep-time dom))
+ (cook-time . ,(org-chef-martha-stewart-extract-cook-time dom))
+ (ready-in . ,(org-chef-martha-stewart-extract-ready-in dom))
+ (directions . ,(org-chef-martha-stewart-extract-directions dom))
+ (source-url . ,url))))
+
+
+(provide 'org-chef-martha-stewart)
+;;; org-chef-martha-stewart.el ends here
--- /dev/null
+;;; org-chef-nytimes.el --- Functions for fetching recipes from nytimes.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck/Asilata Bapat
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>/Asilata Bapat <asilataNOSPAMalum.mit.edu>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck/Asilata Bapat
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from nytimes.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-nytimes-extract-name (dom)
+ "Get the name of a recipe from an nytimes DOM."
+ (string-trim (dom-text (car (dom-by-class dom "recipe-title")))))
+
+
+(defun org-chef-nytimes-ingredient-to-string (dom)
+ "Extract string representation of a single ingredient from an nytimes DOM."
+ (string-join (org-chef-remove-empty-strings (mapcar 'string-trim (dom-strings dom))) " "))
+
+(defun org-chef-nytimes-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an nytimes DOM."
+ (mapcar 'org-chef-nytimes-ingredient-to-string
+ (dom-by-tag (car (dom-elements dom 'class "^recipe-ingredients$")) 'li)))
+
+(defun org-chef-nytimes-extract-servings (dom)
+ "Get the number of servings for a recipe from an nytimes DOM."
+ (replace-regexp-in-string " servings" "" (dom-text (car (dom-elements dom 'itemprop "recipeYield")))))
+
+
+(defun org-chef-nytimes-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from an nytimes DOM."
+ "")
+
+
+(defun org-chef-nytimes-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from an nytimes DOM."
+ "")
+
+
+(defun org-chef-nytimes-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from an nytimes DOM."
+ (dom-texts (dom-by-class (cadr (dom-by-tag (dom-by-class dom "recipe-time-yield") 'li)) "recipe-yield-value")))
+
+
+(defun org-chef-nytimes-extract-directions (dom)
+ "Get the directions for a recipe from an nytimes DOM."
+ (mapcar 'dom-texts (dom-by-tag (dom-by-class dom "recipe-steps") 'li)))
+
+
+(defun org-chef-nytimes-from-dom (dom)
+ "Given an nytimes.com DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ (let* ((prep-time (org-chef-nytimes-extract-prep-time dom))
+ (cook-time (org-chef-nytimes-extract-cook-time dom))
+ (total-time (org-chef-nytimes-extract-ready-in dom)))
+
+ `((ingredients . ,(org-chef-nytimes-extract-ingredients dom))
+ (name . ,(org-chef-nytimes-extract-name dom))
+ (servings . ,(org-chef-nytimes-extract-servings dom))
+ (prep-time . ,prep-time)
+ (cook-time . ,cook-time)
+ (ready-in . ,total-time)
+ (directions . ,(org-chef-nytimes-extract-directions dom)))))
+
+
+(defun org-chef-nytimes-fetch (url)
+ "Given an nytimes.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-nytimes-from-dom dom))))
+
+
+(provide 'org-chef-nytimes)
+;;; org-chef-nytimes.el ends here
--- /dev/null
+(define-package "org-chef" "20231127.1601" "Cookbook and recipe management with org-mode."
+ '((org "0")
+ (emacs "24"))
+ :commit "1710b54441ed744dcdfb125d08fb88cfaf452f10" :authors
+ '(("Calvin Beck" . "hobbesNOSPAMualberta.ca"))
+ :maintainers
+ '(("Calvin Beck" . "hobbesNOSPAMualberta.ca"))
+ :maintainer
+ '("Calvin Beck" . "hobbesNOSPAMualberta.ca")
+ :keywords
+ '("convenience" "abbrev" "outlines" "org" "food" "recipes" "cooking")
+ :url "https://github.com/Chobbes/org-chef")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+
+;;; org-chef-recipetin-eats.el --- Functions for fetching recipes from recipetineats.com -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from www.recipetineats.com
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-recipetin-eats-extract-name (dom)
+ "Get the name of a recipe from a recipetin-eats DOM."
+ (dom-text (car (dom-by-class dom "^wprm-recipe-name"))))
+
+
+(defun org-chef-recipetin-eats-extract-ingredients (dom)
+ "Get the ingredients for a recipe from a RecipeTin Eats DOM."
+ (mapcar #'(lambda (n)
+ (let ((notes (dom-text (dom-by-class n "wprm-recipe-ingredient-notes")))
+ (unit (dom-text (dom-by-class n "wprm-recipe-ingredient-unit"))))
+ (concat (dom-text (dom-by-class n "wprm-recipe-ingredient-amount"))
+ " "
+ unit
+ (if (string= unit "") "" " ")
+ (dom-text (dom-by-class n "wprm-recipe-ingredient-name"))
+ (if (or (zerop (length notes))
+ (= (elt notes 0) ?,))
+ "" ", ")
+ notes)))
+ (dom-by-class dom "^wprm-recipe-ingredient$")))
+
+
+(defun org-chef-recipetin-eats-extract-servings (dom)
+ "Get the number of servings for a recipe from a RecipeTin Eats DOM."
+ (dom-attr (car (dom-by-class dom "wprm-recipe-container")) 'data-servings))
+
+
+(defun org-chef-recipetin-eats-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from a RecipeTin Eats DOM."
+ (let ((span (nth 1 (dom-children
+ (car (dom-by-class dom "wprm-recipe-prep-time-container"))))))
+ (org-chef-join (dom-strings span) "")))
+
+
+(defun org-chef-recipetin-eats-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from a RecipeTin Eats DOM."
+ (let ((span (nth 1 (dom-children
+ (car (dom-by-class dom "wprm-recipe-cook-time-container"))))))
+ (org-chef-join (dom-strings span) "")))
+
+
+(defun org-chef-recipetin-eats-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from a RecipeTin Eats DOM."
+ (let ((span (nth 1 (dom-children
+ (car (dom-by-class dom "wprm-recipe-total-time-container"))))))
+ (org-chef-join (dom-strings span) "")))
+
+
+(defun org-chef-recipetin-eats-extract-directions (dom)
+ "Get the directions for a recipe from a RecipeTin Eats DOM."
+ (org-chef-remove-empty-strings (mapcar #'(lambda (n) (string-trim (dom-texts n))) (dom-by-class dom "^wprm-recipe-instruction$"))))
+
+
+(defun org-chef-recipetin-eats-fetch (url)
+ "Given a recipetineats.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ `((name . ,(org-chef-recipetin-eats-extract-name dom))
+ (ingredients . ,(org-chef-recipetin-eats-extract-ingredients dom))
+ (servings . ,(org-chef-recipetin-eats-extract-servings dom))
+ (prep-time . ,(org-chef-recipetin-eats-extract-prep-time dom))
+ (cook-time . ,(org-chef-recipetin-eats-extract-cook-time dom))
+ (ready-in . ,(org-chef-recipetin-eats-extract-ready-in dom))
+ (directions . ,(org-chef-recipetin-eats-extract-directions dom))
+ (source-url . ,url))))
+
+
+(provide 'org-chef-recipetin-eats)
+;;; org-chef-recipetin-eats.el ends here
--- /dev/null
+;;; org-chef-reluctant-gourmet.el --- Functions for fetching recipes from reluctantgourmet.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from reluctantgourmet.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-reluctant-gourmet-extract-name (dom)
+ "Get the name of a recipe from an reluctantgourmet DOM."
+ (dom-text (car (dom-by-id dom "zlrecipe-title"))))
+
+
+(defun org-chef-reluctant-gourmet-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an reluctantgourmet DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-text n))) (dom-by-class dom "^ingredient$")))
+
+
+(defun org-chef-reluctant-gourmet-extract-servings (dom)
+ "Get the number of servings for a recipe from an reluctantgourmet DOM."
+ (dom-texts (car (dom-elements dom 'itemprop "^recipeYield$"))))
+
+
+(defun org-chef-reluctant-gourmet-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from an reluctantgourmet DOM."
+ (dom-texts (car (dom-elements dom 'itemprop "^prepTime$"))))
+
+
+(defun org-chef-reluctant-gourmet-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from an reluctantgourmet DOM."
+ (dom-texts (car (dom-elements dom 'itemprop "^cookTime$"))))
+
+
+(defun org-chef-reluctant-gourmet-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from an reluctantgourmet DOM."
+ (dom-texts (car (dom-elements dom 'itemprop "^totalTime$"))))
+
+
+(defun org-chef-reluctant-gourmet-extract-directions (dom)
+ "Get the directions for a recipe from an reluctantgourmet DOM."
+ (mapcar #'dom-text (seq-filter (lambda (x) (not (stringp x)))
+ (dom-elements dom 'itemprop "^recipeInstructions$"))))
+
+
+(defun org-chef-reluctant-gourmet-from-dom (dom)
+ "Given a reluctantgourmet.com DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ (let* ((prep-time (org-chef-reluctant-gourmet-extract-prep-time dom))
+ (cook-time (org-chef-reluctant-gourmet-extract-cook-time dom))
+ (total-time (org-chef-reluctant-gourmet-extract-ready-in dom)))
+
+ `((ingredients . ,(org-chef-reluctant-gourmet-extract-ingredients dom))
+ (name . ,(org-chef-reluctant-gourmet-extract-name dom))
+ (servings . ,(org-chef-reluctant-gourmet-extract-servings dom))
+ (prep-time . ,prep-time)
+ (cook-time . ,cook-time)
+ (ready-in . ,total-time)
+ (directions . ,(org-chef-reluctant-gourmet-extract-directions dom)))))
+
+
+(defun org-chef-reluctant-gourmet-fetch (url)
+ "Given a reluctantgourmet.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-reluctant-gourmet-from-dom dom))))
+
+
+(provide 'org-chef-reluctant-gourmet)
+;;; org-chef-reluctant-gourmet.el ends here
--- /dev/null
+;;; org-chef-saveur.el --- Functions for fetching recipes from saveur.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck/Derek Ellis
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>/Derek Ellis <derek.ellisNOSPAMgmail.com>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck/Derek Ellis
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from saveur.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+ (defun org-chef-saveur-chomp (str)
+ "Chomp leading and tailing whitespace from STR."
+ (replace-regexp-in-string (rx (or (: bos (* (any " \t\n")))
+ (: (* (any " \t\n")) eos)))
+ ""
+ str))
+
+
+(defun org-chef-saveur-extract-name (dom)
+ "Get the name of a recipe from an saveur DOM."
+ (string-trim (dom-text (car (dom-by-class dom "page-title")))))
+
+
+(defun org-chef-saveur-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an saveur DOM."
+ (remove ""
+ (mapcar 'org-chef-saveur-chomp
+ (split-string
+ (replace-regexp-in-string " ⁄ " "/"
+ (string-join (mapcar #'dom-texts (dom-by-class dom "^ingredients$")))) "\n" ))))
+
+
+(defun org-chef-saveur-extract-servings (dom)
+ "Get the number of servings for a recipe from an saveur DOM."
+ (replace-regexp-in-string "serves " ""
+ (dom-text (dom-by-tag (dom-by-class dom "yield") 'span))))
+
+
+(defun org-chef-saveur-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from an saveur DOM."
+ (org-chef-saveur-chomp
+ (dom-text (dom-by-class dom "^prep-time$"))))
+
+
+(defun org-chef-saveur-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from an saveur DOM."
+ (org-chef-saveur-chomp
+ (dom-text (dom-by-class dom "^cook-time$"))))
+
+
+(defun org-chef-saveur-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from an saveur DOM."
+ (string-trim (dom-texts (dom-by-class (cadr (dom-by-tag (dom-by-class dom "recipe-time-yield") 'li)) "recipe-yield-value"))))
+
+
+(defun org-chef-saveur-extract-directions (dom)
+ "Get the directions for a recipe from an saveur DOM."
+ (remove ""
+ (mapcar 'org-chef-saveur-chomp
+ (split-string
+ (replace-regexp-in-string " ⁄ " "/"
+ (dom-texts
+ (dom-by-class dom "^instructions "))) "\n \n " ))))
+
+(defun org-chef-saveur-from-dom (dom)
+ "Given an saveur.com DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ (let* ((prep-time (org-chef-saveur-extract-prep-time dom))
+ (cook-time (org-chef-saveur-extract-cook-time dom))
+ (total-time (org-chef-saveur-extract-ready-in dom)))
+
+ `((ingredients . ,(org-chef-saveur-extract-ingredients dom))
+ (name . ,(org-chef-saveur-extract-name dom))
+ (servings . ,(org-chef-saveur-extract-servings dom))
+ (prep-time . ,prep-time)
+ (cook-time . ,cook-time)
+ (ready-in . ,total-time)
+ (directions . ,(org-chef-saveur-extract-directions dom)))))
+
+
+(defun org-chef-saveur-fetch (url)
+ "Given an saveur.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-saveur-from-dom dom))))
+
+
+(provide 'org-chef-saveur)
+;;; org-chef-saveur.el ends here
--- /dev/null
+;;; org-chef-serious-eats.el --- org-chef seriouseats fetching. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from seriouseats.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-serious-eats-extract-meta (dom name)
+ "Extract a metadata element from the serious eats dom."
+ (string-trim (dom-texts (dom-elements (dom-elements dom 'class (concat "project-meta__" name)) 'class "meta-text__data"))))
+
+(defun org-chef-serious-eats-extract-name (dom)
+ "Get the name of a recipe from an seriouseats DOM."
+ (dom-text (car (dom-elements dom 'class "heading__title"))))
+
+(defun org-chef-serious-eats-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an seriouseats DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-texts n)))
+ (dom-by-tag (append
+ (dom-by-class dom "structured-ingredients__list")
+ (dom-by-class dom "ingredient-list"))
+ 'li)))
+
+(defun org-chef-serious-eats-extract-servings (dom)
+ "Get the number of servings for a recipe from an seriouseats DOM."
+ (org-chef-serious-eats-extract-meta dom "recipe-yield"))
+
+(defun org-chef-serious-eats-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from an seriouseats DOM."
+ (org-chef-serious-eats-extract-meta dom "active-time"))
+
+(defun org-chef-serious-eats-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from an seriouseats DOM."
+ nil)
+
+(defun org-chef-serious-eats-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from an seriouseats DOM."
+ (org-chef-serious-eats-extract-meta dom "total-time"))
+
+(defun org-chef-serious-eats-extract-directions (dom)
+ "Get the directions for a recipe from an seriouseats DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-texts n)))
+ (dom-by-tag (dom-by-class (dom-by-class dom "section--instructions") "structured-project__steps") 'li)))
+
+(defun org-chef-serious-eats-fetch (url)
+ "Given an seriouseats.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ `((name . ,(org-chef-serious-eats-extract-name dom))
+ (ingredients . ,(org-chef-serious-eats-extract-ingredients dom))
+ (servings . ,(org-chef-serious-eats-extract-servings dom))
+ (prep-time . ,(org-chef-serious-eats-extract-prep-time dom))
+ (cook-time . ,(org-chef-serious-eats-extract-cook-time dom))
+ (ready-in . ,(org-chef-serious-eats-extract-ready-in dom))
+ (directions . ,(org-chef-serious-eats-extract-directions dom))
+ (source-url . ,url))))
+
+(provide 'org-chef-serious-eats)
+;;; org-chef-serious-eats.el ends here
--- /dev/null
+;;; org-chef-simply-recipes.el --- Functions for fetching recipes from simplyrecipes.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from simplyrecipes.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-simply-recipes-extract-name (dom)
+ "Get the name of a recipe from an simplyrecipes DOM."
+ (dom-text (car (dom-by-class dom "entry-title"))))
+
+
+(defun org-chef-simply-recipes-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an simplyrecipes DOM."
+ (mapcar 'dom-text (dom-by-class dom "^ingredient$")))
+
+
+(defun org-chef-simply-recipes-extract-servings (dom)
+ "Get the number of servings for a recipe from an simplyrecipes DOM."
+ (let ((servings (dom-text (car (dom-by-class dom "^yield*")))))
+ (when servings
+ (substring servings 7))))
+
+
+(defun org-chef-simply-recipes-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from an simplyrecipes DOM."
+ (dom-text (car (dom-by-class dom "preptime"))))
+
+
+(defun org-chef-simply-recipes-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from an simplyrecipes DOM."
+ (let ((cook-time (dom-by-class dom "cooktime"))
+ (other-time (dom-by-class dom "othertime")))
+ (dom-text (car (if (null cook-time) other-time cook-time)))))
+
+
+(defun org-chef-simply-recipes-extract-directions (dom)
+ "Get the directions for a recipe from an simplyrecipes DOM."
+ (mapcar #'dom-text (seq-filter (lambda (x) (not (stringp x)))
+ (dom-children (car (dom-elements dom 'itemprop "^recipeInstructions$"))))))
+
+
+(defun org-chef-simply-recipes-from-dom (dom)
+ "Given a simplyrecipes.com DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ (let* ((prep-time (org-chef-simply-recipes-extract-prep-time dom))
+ (cook-time (org-chef-simply-recipes-extract-cook-time dom)))
+
+ `((ingredients . ,(org-chef-simply-recipes-extract-ingredients dom))
+ (name . ,(org-chef-simply-recipes-extract-name dom)
+ )
+ (servings . ,(org-chef-simply-recipes-extract-servings dom))
+ (prep-time . ,prep-time)
+ (cook-time . ,cook-time)
+ (ready-in . nil)
+ (directions . ,(org-chef-simply-recipes-extract-directions dom)))))
+
+
+(defun org-chef-simply-recipes-fetch (url)
+ "Given a simplyrecipes.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-simply-recipes-from-dom dom))))
+
+
+(provide 'org-chef-simply-recipes)
+;;; org-chef-simply-recipes.el ends here
--- /dev/null
+;;; org-chef-taste.el --- org-chef taste.com.au fetching -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019 Calvin Beck, Peter Hardy
+
+;; Author: Peter Hardy <peterNOSPAMhardy.dropbear.id.au>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2019
+
+;; Copyright 2019 Peter Hardy
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from taste.com.au.
+
+;;; Code:
+
+(require 'org-chef-utils)
+(require 'cl-seq)
+(require 'dom)
+
+(defun org-chef-taste-extract-cooking-infos (dom info)
+ "Get INFO from recipe-cooking-infos in a taste.com.au DOM."
+ (let ((i (cl-position info (mapcar #'(lambda (n) (string-trim (dom-text n))) (dom-by-tag (dom-by-class dom "^recipe-cooking-infos$") 'li)) :test 'equal)))
+ (if i
+ (dom-text (nth i (dom-by-tag (dom-by-class dom "^recipe-cooking-infos$") 'b)))
+ "")))
+
+(defun org-chef-taste-extract-name (dom)
+ "Get the name of a recipe from a taste.com.au DOM."
+ (dom-text (dom-by-tag dom 'h1)))
+
+(defun org-chef-taste-extract-ingredients (dom)
+ "Get the ingredients for a recipe from a taste.com.au DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-texts n)))
+ (dom-by-class dom "^ingredient-description$")))
+
+(defun org-chef-taste-extract-servings (dom)
+ "Get the servings for a recipe from a taste.com.au DOM."
+ (let ((servings (org-chef-taste-extract-cooking-infos dom "Servings")))
+ (if (string-equal servings "")
+ (org-chef-taste-extract-cooking-infos dom "Makes")
+ servings)))
+
+(defun org-chef-taste-extract-prep-time (dom)
+ "Get the prep time for a recipe from a taste.com.au DOM."
+ (org-chef-taste-extract-cooking-infos dom "Prep"))
+
+(defun org-chef-taste-extract-cook-time (dom)
+ "Get the prep time for a recipe from a taste.com.au DOM."
+ (org-chef-taste-extract-cooking-infos dom "Cook"))
+
+(defun org-chef-taste-extract-directions (dom)
+ "Get the directions for a recipe from a taste.com.au DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-texts n)))
+ (dom-by-class dom "^recipe-method-step-content$")))
+
+(defun org-chef-taste-fetch (url)
+ "Given a taste.com.au URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ `((name . ,(org-chef-taste-extract-name dom))
+ (ingredients . ,(org-chef-taste-extract-ingredients dom))
+ (servings . ,(org-chef-taste-extract-servings dom))
+ (prep-time . ,(org-chef-taste-extract-prep-time dom))
+ (cook-time . ,(org-chef-taste-extract-cook-time dom))
+ (ready-in . "")
+ (directions . ,(org-chef-taste-extract-directions dom))
+ (source-url . ,url))))
+
+(provide 'org-chef-taste)
+;;; org-chef-taste.el ends here
--- /dev/null
+;;; org-chef-utils.el --- Utilities for org-chef. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Utility functions for org-chef.
+
+;;; Code:
+
+(require 'cl-macs)
+(require 'gnutls)
+(require 'dom)
+
+(defun org-chef-remove-empty-strings (lst)
+ "Filter out any empty strings in a list of strings (LST)."
+ (seq-filter (lambda (x) (/= (length x) 0)) lst))
+
+
+(defun org-chef-dom-children (dom)
+ "Get the child nodes of the DOM, remove strings."
+ (seq-filter (lambda (x) (not (stringp x)))
+ (dom-children dom)))
+
+(defun org-chef-render-html (text)
+ "Turn an html string into normal text."
+ (with-temp-buffer
+ (insert text)
+ (shr-render-buffer (current-buffer))
+ (buffer-string)))
+
+(defun org-chef-insert-org-list (lst &optional bullet)
+ "Insert LST as an ‘org-mode’ plain list.
+
+The optional argument BULLET specifies which type of bullet point
+should be used."
+ (mapcar (lambda (x) (progn (insert (format "%s " (string-trim x)))
+ (org-cycle)
+ (org-delete-backward-char 1)
+ (org-ctrl-c-minus)
+ (if bullet (org-cycle-list-bullet bullet))
+ (org-return)))
+ lst))
+
+
+(defun org-chef-url-retrieve-synchronously (url)
+ "Fetch URL using url-retrieve-synchronously.
+
+This is a wrapper for url-retrieve-synchronously, which primarily serves to implement workarounds."
+
+ (let ((gnutls-algorithm-priority
+ (if org-chef-fetch-workaround
+ "NORMAL:-VERS-TLS1.3"
+ gnutls-algorithm-priority)))
+
+ (url-retrieve-synchronously url)))
+
+
+(defun org-chef-string-to-dom (xml)
+ (with-temp-buffer
+ (insert xml)
+ (xml-parse-region (point-min) (point-max))))
+
+
+(defun org-chef-url-retrieve-dom (url)
+ "Fetch URL synchronously, and parse into a DOM structure"
+ (with-current-buffer (org-chef-url-retrieve-synchronously url)
+ (goto-char (point-min))
+ (search-forward "\n\n") ; skip past HTTP headers
+ (let ((result (libxml-parse-html-region (point) (point-max))))
+ (kill-buffer) ; don't leak buffer
+ result)))
+
+
+(defun org-chef-join (strings separator)
+ "Joins a list of strings using a separator"
+ (mapconcat #'identity strings separator))
+
+
+;; Adapted from https://emacs.stackexchange.com/a/7150
+(defun org-chef-regexp-matches (regexp string)
+ "Get a list of all regexp matches in a string"
+ (save-match-data
+ (let ((pos 0) stop matches)
+ (while (and (not stop) (string-match regexp string pos))
+ (push (match-string 0 string) matches)
+ (when (= (match-end 0) pos)
+ (setq stop t))
+ (setq pos (match-end 0)))
+ (nreverse matches))))
+
+
+(provide 'org-chef-utils)
+;;; org-chef-utils.el ends here
--- /dev/null
+;;; org-chef-wordpress.el --- org-chef wordpress fetching. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from wordpress sites.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+
+(defun org-chef-wordpress-extract-name (dom)
+ "Get the name of a recipe from an wordpress DOM."
+ (dom-text (car (dom-by-class dom "^wprm-recipe-name"))))
+
+
+(defun org-chef-wordpress-extract-ingredients (dom)
+ "Get the ingredients for a recipe from an wordpress DOM."
+ (mapcar #'(lambda (n) (string-trim (dom-texts n))) (dom-by-class dom "^wprm-recipe-ingredient$")))
+
+
+(defun org-chef-wordpress-extract-servings (dom)
+ "Get the number of servings for a recipe from an wordpress DOM."
+ (dom-texts (car (dom-by-class dom "wprm-recipe-servings$"))))
+
+
+(defun org-chef-wordpress-extract-prep-time (dom)
+ "Get the amount of prep-time for a recipe from an wordpress DOM."
+ (dom-texts (car (dom-by-class dom "^wprm-recipe-time$"))))
+
+
+(defun org-chef-wordpress-extract-cook-time (dom)
+ "Get the amount of cook-time for a recipe from an wordpress DOM."
+ (dom-texts (cadr (dom-by-class dom "^wprm-recipe-time$"))))
+
+
+(defun org-chef-wordpress-extract-ready-in (dom)
+ "Get the total amount of time for a recipe from an wordpress DOM."
+ (dom-texts (caddr (dom-by-class dom "^wprm-recipe-time$"))))
+
+
+(defun org-chef-wordpress-extract-directions (dom)
+ "Get the directions for a recipe from an wordpress DOM."
+ (org-chef-remove-empty-strings (mapcar #'(lambda (n) (string-trim (dom-texts n))) (dom-by-class dom "^wprm-recipe-instruction$"))))
+
+
+(defun org-chef-is-wordpress-p (dom)
+ "Check if a DOM corresponds to a wordpress website."
+ (dom-by-class dom "^wprm-recipe-container$"))
+
+
+(defun org-chef-wordpress-fetch (url)
+ "Given an wordpress URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url
+
+If this is not a wordpress site, then return nil."
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (if (org-chef-is-wordpress-p dom)
+ `((name . ,(org-chef-wordpress-extract-name dom))
+ (ingredients . ,(org-chef-wordpress-extract-ingredients dom))
+ (servings . ,(org-chef-wordpress-extract-servings dom))
+ (prep-time . ,(org-chef-wordpress-extract-prep-time dom))
+ (cook-time . ,(org-chef-wordpress-extract-cook-time dom))
+ (ready-in . ,(org-chef-wordpress-extract-ready-in dom))
+ (directions . ,(org-chef-wordpress-extract-directions dom))
+ (source-url . ,url))
+ nil)))
+
+
+(provide 'org-chef-wordpress)
+;;; org-chef-wordpress.el ends here
--- /dev/null
+;;; org-chef-xiachufang.el --- Functions for fetching recipes from xiachufang.com. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck/Sébastien Le Maguer
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>/Sébastien Le Maguer <seb.lemaguerNOSPAMgmail.com>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+
+;; Copyright 2019 Yiu Fung Cheong
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; Functions for fetching information from xiachufang.com.
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'dom)
+
+(defun org-chef-xiachufang-sanitize (str)
+ "Sanitize the STR by removing beginning/trailing spaces extracted from a marmiton dom."
+ (replace-regexp-in-string "^[ \r\n]*\\(.*[^ \r\n]\\)[ \r\n]*$" "\\1" str))
+
+(defun org-chef-xiachufang-extract-name (dom)
+ "Get the name of a recipe from a xiachufang DOM."
+ (org-chef-xiachufang-sanitize (dom-text (car (dom-elements dom 'class "^page-title$")))))
+
+
+(defun org-chef-xiachufang-extract-ingredients (dom)
+ "Get the ingredients for a recipe from a xiachufang DOM."
+ (mapcar #'(lambda (n)
+ (replace-regexp-in-string "\n" " " (org-chef-xiachufang-sanitize (dom-texts n))))
+ (dom-elements dom 'itemprop "^recipeIngredient$")))
+
+
+(defun org-chef-xiachufang-extract-directions (dom)
+ "Get the directions for a recipe from a xiachufang DOM."
+ (mapcar #'(lambda (n)
+ (replace-regexp-in-string "\n" " " (org-chef-xiachufang-sanitize (dom-texts n))))
+ (dom-elements dom 'itemprop "^recipeInstructions$")))
+
+
+(defun org-chef-xiachufang-extract-prep-time (dom)
+ "Get the preparation time for a recipe from a xiachufang DOM."
+ "")
+
+
+(defun org-chef-xiachufang-extract-cook-time (dom)
+ "Extract the cooking time for a recipe from a xiachufang DOM."
+ "")
+
+
+(defun org-chef-xiachufang-extract-servings (dom)
+ "")
+
+
+(defun org-chef-xiachufang-from-dom (dom)
+ "Given a xiachufang.com DOM, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions"
+ `((ingredients . ,(org-chef-xiachufang-extract-ingredients dom))
+ (name . ,(org-chef-xiachufang-extract-name dom))
+ (servings . ,(org-chef-xiachufang-extract-servings dom))
+ (prep-time . ,(org-chef-xiachufang-extract-prep-time dom))
+ (cook-time . ,(org-chef-xiachufang-extract-cook-time dom))
+ (ready-in . nil)
+ (directions . ,(org-chef-xiachufang-extract-directions dom))))
+
+
+(defun org-chef-xiachufang-fetch (url)
+ "Given a xiachufang.com URL, retrieve the recipe information.
+
+This returns an alist with the following keys:
+
+- name
+- ingredients
+- servings
+- prep-time
+- cook-time
+- ready-in
+- directions
+- source-url"
+ (let ((dom (org-chef-url-retrieve-dom url)))
+ (cons `(source-url . ,url) (org-chef-xiachufang-from-dom dom))))
+
+
+(provide 'org-chef-xiachufang)
+;;; org-chef-xiachufang.el ends here
--- /dev/null
+;;; org-chef.el --- Cookbook and recipe management with org-mode. -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2018 Calvin Beck
+
+;; Author: Calvin Beck <hobbesNOSPAMualberta.ca>
+;; URL: https://github.com/Chobbes/org-chef
+;; Created: 2018
+;; Version: 0.1.3
+;; Keywords: convenience, abbrev, outlines, org, food, recipes, cooking
+;; Package-Requires: ((org "0") (emacs "24"))
+
+;; Copyright 2018 Calvin Beck
+
+;; Permission is hereby granted, free of charge, to any person
+;; obtaining a copy of this software and associated documentation
+;; files (the "Software"), to deal in the Software without
+;; restriction, including without limitation the rights to use, copy,
+;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;; of the Software, and to permit persons to whom the Software is
+;; furnished to do so, subject to the following conditions:
+
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+;; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+;; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+;; SOFTWARE.
+
+;;; Commentary:
+
+;; org-chef is a package for managing recipes in org-mode. One of the
+;; main features is that it can automatically extract recipes from
+;; websites like allrecipes.com
+
+;;; Code:
+
+
+(require 'org-chef-utils)
+(require 'org-chef-edit)
+
+(require 'org-chef-24kitchen)
+(require 'org-chef-json-ld)
+(require 'org-chef-genius-kitchen)
+(require 'org-chef-simply-recipes)
+(require 'org-chef-martha-stewart)
+(require 'org-chef-cultures-for-health)
+(require 'org-chef-fine-cooking)
+(require 'org-chef-marmiton)
+(require 'org-chef-serious-eats)
+(require 'org-chef-reluctant-gourmet)
+(require 'org-chef-chef-koch)
+(require 'org-chef-nytimes)
+(require 'org-chef-saveur)
+(require 'org-chef-xiachufang)
+(require 'org-chef-wordpress)
+(require 'org-chef-taste)
+(require 'org-chef-bbc-food)
+(require 'org-chef-bbc-good-food)
+(require 'org-chef-jamie-oliver)
+(require 'org-chef-recipetin-eats)
+(require 'org-chef-binging-with-babish)
+(require 'org-chef-basics-with-babish)
+(require 'org-chef-kotikokki)
+
+
+(defvar org-chef-fetch-workaround
+ (and
+ ;; Note: For build sans gnutls, `libgnutls-version' is -1.
+ (>= libgnutls-version 30603)
+ (version<= emacs-version "26.2")
+ 't)
+ "Inspired by https://github.com/magit/ghub/blob/7d59937d7782d0062216130a4d059b45e8396f82/ghub.el#L452
+
+See https://github.com/magit/ghub/issues/81 and https://debbugs.gnu.org/cgi/bugreport.cgi?bug=34341
+for more information.")
+
+(defcustom org-chef-prefer-json-ld nil
+ "Prefer JSON-LD extractor over custom extractor. This is for testing the JSON-LD functionality."
+ :type 'boolean)
+
+(defun org-chef-to-unordered-list (list)
+ "Convert a LIST of strings into an org-element plain list"
+ (if (null list)
+ nil
+ `(plain-list nil ,(mapcar #'(lambda (x) `(item (:bullet "-" :pre-blank 0) ,x)) list))))
+
+(defun org-chef-to-ordered-list (list)
+ "Convert a LIST of strings into an ordered org-element plain list"
+ (if (null list)
+ nil
+ `(plain-list nil ,(mapcar #'(lambda (x) `(item (:bullet "1. " :pre-blank 0) ,x)) list))))
+
+(defun org-chef-recipe-to-org-element (recipe)
+ "Convert a RECIPE into an `org-element` AST."
+ `(headline (:title ,(cdr (assoc 'name recipe)) :level 1)
+ (property-drawer nil
+ ((node-property (:key "source-url" :value ,(cdr (assoc 'source-url recipe))))
+ (node-property (:key "servings" :value ,(cdr (assoc 'servings recipe))))
+ (node-property (:key "prep-time" :value ,(format "%s" (cdr (assoc 'prep-time recipe)))))
+ (node-property (:key "cook-time" :value ,(format "%s" (cdr (assoc 'cook-time recipe)))))
+ (node-property (:key "ready-in" :value ,(format "%s" (cdr (assoc 'ready-in recipe)))))))
+ (headline (:title "Ingredients" :level 2 :pre-blank 1)
+ ,(org-chef-to-unordered-list (cdr (assoc 'ingredients recipe))))
+ (headline (:title "Directions" :level 2 :pre-blank 1)
+ ,(org-chef-to-ordered-list (cdr (assoc 'directions recipe))))))
+
+(defun org-chef-recipe-to-org (recipe)
+ "Convert a RECIPE into `org-mode` test."
+ (org-element-interpret-data (org-chef-recipe-to-org-element recipe)))
+
+(defun org-chef-recipe-insert-org (recipe)
+ "Insert a RECIPE as an ‘org-mode’ heading."
+ (insert
+ (org-chef-recipe-to-org recipe)))
+
+(defun org-chef-recipe-org-string (recipe)
+ "Get an ‘org-mode’ heading string for a RECIPE."
+ (with-temp-buffer (org-mode)
+ (org-chef-recipe-insert-org recipe)
+ (buffer-string)))
+
+(defun org-chef-match-url (BASE URL)
+ "Match URL against a BASE url."
+ (not (null (string-match-p (regexp-quote BASE) URL))))
+
+(defun org-chef-fetch-recipe-specific-url (URL)
+ "Look up a recipe based on a specific URL."
+ (cond
+ ((org-chef-match-url "24kitchen.nl" URL) (org-chef-24kitchen-fetch URL))
+ ((org-chef-match-url "geniuskitchen.com" URL) (org-chef-genius-kitchen-fetch URL))
+ ((org-chef-match-url "simplyrecipes.com" URL) (org-chef-simply-recipes-fetch URL))
+ ((org-chef-match-url "marthastewart.com" URL) (org-chef-martha-stewart-fetch URL))
+ ((org-chef-match-url "culturesforhealth.com" URL) (org-chef-cultures-for-health-fetch URL))
+ ((org-chef-match-url "marmiton.org" URL) (org-chef-marmiton-fetch URL))
+ ((org-chef-match-url "seriouseats.com" URL) (org-chef-serious-eats-fetch URL))
+ ((org-chef-match-url "reluctantgourmet.com" URL) (org-chef-reluctant-gourmet-fetch URL))
+ ((org-chef-match-url "chefkoch.de" URL) (org-chef-chef-koch-fetch URL))
+ ((org-chef-match-url "nytimes.com" URL) (org-chef-nytimes-fetch URL))
+ ((org-chef-match-url "saveur.com" URL) (org-chef-saveur-fetch URL))
+ ((org-chef-match-url "xiachufang.com" URL) (org-chef-xiachufang-fetch URL))
+ ((org-chef-match-url "finecooking.com" URL) (org-chef-fine-cooking-fetch URL))
+ ((org-chef-match-url "taste.com.au" URL) (org-chef-taste-fetch URL))
+ ((org-chef-match-url "bbc.co.uk/food/" URL) (org-chef-bbc-food-fetch URL))
+ ((org-chef-match-url "bbcgoodfood.com" URL) (org-chef-bbc-good-food-fetch URL))
+ ((org-chef-match-url "jamieoliver.com" URL) (org-chef-jamie-oliver-fetch URL))
+ ((org-chef-match-url "recipetineats.com" URL) (org-chef-recipetin-eats-fetch URL))
+ ((org-chef-match-url "basicswithbabish.co" URL) (org-chef-basics-with-babish-fetch URL))
+ ((org-chef-match-url "bingingwithbabish.com" URL) (org-chef-binging-with-babish-fetch URL))
+ ((org-chef-match-url "kotikokki.net" URL) (org-chef-kotikokki-fetch URL))
+ (t nil)))
+
+
+(defun org-chef-fetch-recipe (URL)
+ "Look up a recipe at a URL."
+ (or (and org-chef-prefer-json-ld
+ (org-chef-json-ld-fetch URL))
+ (org-chef-fetch-recipe-specific-url URL)
+ (org-chef-wordpress-fetch URL)
+ (and (not org-chef-prefer-json-ld)
+ (org-chef-json-ld-fetch URL))))
+
+;;;###autoload
+(defun org-chef-insert-recipe (URL)
+ "Prompt for a recipe URL, and then insert the recipe at point."
+ (interactive "sRecipe URL: ")
+ (org-chef-recipe-insert-org (org-chef-fetch-recipe URL)))
+
+
+;;;###autoload
+(defun org-chef-get-recipe-from-url ()
+ "Prompt for a recipe URL, and return the ‘org-mode’ string."
+ (let ((URL (read-string "Recipe URL: ")))
+ (org-chef-recipe-org-string (org-chef-fetch-recipe URL))))
+
+
+(provide 'org-chef)
+;;; org-chef.el ends here