A place to cache linked articles (think custom and personal wayback machine)
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

index.html 16KB

2 år sedan
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Cooklang - Managing Recipes in Git (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Documented, feel free to shoot an email. -->
  28. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  29. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  30. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  31. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  32. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  36. <script>
  37. function toggleTheme(themeName) {
  38. document.documentElement.classList.toggle(
  39. 'forced-dark',
  40. themeName === 'dark'
  41. )
  42. document.documentElement.classList.toggle(
  43. 'forced-light',
  44. themeName === 'light'
  45. )
  46. }
  47. const selectedTheme = localStorage.getItem('theme')
  48. if (selectedTheme !== 'undefined') {
  49. toggleTheme(selectedTheme)
  50. }
  51. </script>
  52. <meta name="robots" content="noindex, nofollow">
  53. <meta content="origin-when-cross-origin" name="referrer">
  54. <!-- Canonical URL for SEO purposes -->
  55. <link rel="canonical" href="https://briansunter.com/blog/cooklang/">
  56. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  57. <article>
  58. <header>
  59. <h1>Cooklang - Managing Recipes in Git</h1>
  60. </header>
  61. <nav>
  62. <p class="center">
  63. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  64. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  65. </svg> Accueil</a> •
  66. <a href="https://briansunter.com/blog/cooklang/" title="Lien vers le contenu original">Source originale</a>
  67. </p>
  68. </nav>
  69. <hr>
  70. <p>Cooklang is like markdown for recipes. It lets you write recipes in a human-readable format that a computer can parse to get the ingredient list, steps, etc.</p>
  71. <p>Recipe sites have gotten so bloated that you need to read someone's entire life story to get the ingredients and steps. I'm a big fan of storing information in plain text in git, as well as <a href="https://briansunter.com/notes/cooking/">[[cooking]]</a>, so I was happy to discover <a href="https://cooklang.org/" target="_blank" rel="noopener noreferrer">CookLang</a>.</p>
  72. <p>I don't want to write my recipes in completely unstructured text, because then I can't do interesting things like generate shopping lists, convert units, get required cookware, etc. I also don't want to write them in a highly structured format like JSON because it would be too difficult to read and maintain.</p>
  73. <p>I like cooklang because you can read it like a recipe if you want. I also like that you can list the ingredients throughout the steps and it automatically builds an ingredients list. This way you don't have to maintain a dedicated ingredients list and it's easier to update.</p>
  74. <h2 id="cooklang-recipe" tabindex="-1"></h2>
  75. <p>This is what a cooklang recipe looks like.</p>
  76. <pre class="language-ruby"><code class="language-ruby"><span class="token operator">&gt;&gt;</span>title<span class="token operator">:</span> Sous Vide Steak<br><span class="token operator">&gt;&gt;</span>description<span class="token operator">:</span> <span class="token constant">A</span> simple consistent way to cook great steak<br><span class="token operator">&gt;&gt;</span>source<span class="token operator">:</span> https<span class="token operator">:</span><span class="token operator">/</span><span class="token operator">/</span>www<span class="token punctuation">.</span>seriouseats<span class="token punctuation">.</span>com<span class="token operator">/</span>food<span class="token operator">-</span>lab<span class="token operator">-</span>complete<span class="token operator">-</span>guide<span class="token operator">-</span>to<span class="token operator">-</span>sous<span class="token operator">-</span>vide<span class="token operator">-</span>steak<br>Preheat a sous vide cooker to desired final temperature<span class="token punctuation">.</span><br><br>Season <span class="token variable">@steaks</span><span class="token punctuation">{</span><span class="token number">450</span><span class="token operator">%</span>g<span class="token punctuation">}</span> generously with <span class="token variable">@salt</span> <span class="token keyword">and</span> <span class="token variable">@pepper</span><br><br>Place <span class="token keyword">in</span> <span class="token variable">@sous</span> vide bags<span class="token punctuation">{</span><span class="token punctuation">}</span> along with <span class="token variable">@thyme</span><span class="token punctuation">{</span><span class="token number">4</span><span class="token operator">%</span>sprigs<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token variable">@rosemary</span><span class="token punctuation">{</span><span class="token number">4</span><span class="token operator">%</span>sprigs<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token variable">@garlic</span><span class="token punctuation">{</span><span class="token number">4</span><span class="token operator">%</span>cloves<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token keyword">and</span> <span class="token variable">@shallots</span><span class="token punctuation">{</span><span class="token number">2</span><span class="token operator">%</span>thinly sliced<span class="token punctuation">}</span> <span class="token keyword">and</span> distribute evenly<span class="token punctuation">.</span> <br><br>Seal bags <span class="token keyword">and</span> place <span class="token keyword">in</span> water bath <span class="token keyword">for</span> desired time according to charts<span class="token punctuation">.</span><br><br>Turn on your vents <span class="token keyword">and</span> open your windows<span class="token punctuation">.</span> Add one tablespoon of ghee<span class="token punctuation">,</span> vegetable<span class="token punctuation">,</span> <br>canola<span class="token punctuation">,</span> <span class="token keyword">or</span> rice bran oil to a heavy <br><span class="token keyword">and</span> preheat the skillet <span class="token keyword">until</span> it reaches <span class="token number">450</span> degrees<span class="token punctuation">.</span><br><br>Gently lay steak <span class="token keyword">in</span> skillet<span class="token punctuation">,</span> using your fingers <span class="token keyword">or</span> a set of tongs<span class="token punctuation">.</span> <br><br>Add <span class="token variable">@butter</span><span class="token punctuation">{</span><span class="token number">30</span><span class="token operator">%</span>g<span class="token punctuation">}</span> <span class="token punctuation">(</span><span class="token number">2</span> tablespoons<span class="token punctuation">)</span><span class="token punctuation">.</span><br><br>After <span class="token number">15</span> to <span class="token operator">~</span><span class="token punctuation">{</span><span class="token number">30</span><span class="token operator">%</span>seconds<span class="token punctuation">}</span><span class="token punctuation">,</span> flip steak so that the second side comes into contact with the pan<span class="token punctuation">.</span><br>Repeat<span class="token punctuation">,</span> flipping steak every <span class="token operator">~</span><span class="token punctuation">{</span><span class="token number">30</span><span class="token operator">%</span>seconds<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token keyword">until</span> it has developed a nice brown sear<span class="token punctuation">,</span> about <span class="token operator">~</span><span class="token punctuation">{</span><span class="token number">1.5</span><span class="token operator">%</span>minutes<span class="token punctuation">}</span> total<span class="token punctuation">.</span><br><br>Serve steak immediately</code></pre>
  77. <h2 id="cooklang-syntax" tabindex="-1"></h2>
  78. <p>Although the text is human readable, this is what the special syntax means.</p>
  79. <h3 id="ingredients" tabindex="-1"></h3>
  80. <p><code>@ingredient{quantity%unit}</code> for example <code>@butter{30%g}</code></p>
  81. <p>Simple ingredients can be specified like <code>@salt.</code></p>
  82. <p>Ingredients with spaces can use curly braces <code>@ground pepper{}</code></p>
  83. <h3 id="time" tabindex="-1"></h3>
  84. <p>Time can be specified with <code>~{quantity%units}</code> like <code>~{30%seconds}</code></p>
  85. <p>Metadata can be added in the form of <code>&gt;&gt;key: value</code></p>
  86. <p><code>&gt;&gt;source: https://www.seriouseats.com/food-lab-complete-guide-to-sous-vide-steak</code></p>
  87. <h3 id="cookware" tabindex="-1"></h3>
  88. <p>You can specify cookware needed with <code>#cookware{}</code> like <code>#potato masher{}</code>.</p>
  89. <h3 id="conclusion" tabindex="-1"></h3>
  90. <p>Overall I'm really happy with cooklang. It was pretty easy to integrate with my static site and the <a href="https://obsidian.md/" target="_blank" rel="noopener noreferrer">Obsidian</a> cooklang plugin works well. I hope to collect my favorite recipes and continue to tune them over time.</p>
  91. <p>See <a href="https://briansunter.com/recipes/">here for my complete list of recipes</a> categories by tag.</p>
  92. </article>
  93. <hr>
  94. <footer>
  95. <p>
  96. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  97. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  98. </svg> Accueil</a> •
  99. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  100. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  101. </svg> Suivre</a> •
  102. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  103. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  104. </svg> Pro</a> •
  105. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  106. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  107. </svg> Email</a> •
  108. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  109. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  110. </svg> Légal</abbr>
  111. </p>
  112. <template id="theme-selector">
  113. <form>
  114. <fieldset>
  115. <legend><svg class="icon icon-brightness-contrast">
  116. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  117. </svg> Thème</legend>
  118. <label>
  119. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  120. </label>
  121. <label>
  122. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  123. </label>
  124. <label>
  125. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  126. </label>
  127. </fieldset>
  128. </form>
  129. </template>
  130. </footer>
  131. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  132. <script>
  133. function loadThemeForm(templateName) {
  134. const themeSelectorTemplate = document.querySelector(templateName)
  135. const form = themeSelectorTemplate.content.firstElementChild
  136. themeSelectorTemplate.replaceWith(form)
  137. form.addEventListener('change', (e) => {
  138. const chosenColorScheme = e.target.value
  139. localStorage.setItem('theme', chosenColorScheme)
  140. toggleTheme(chosenColorScheme)
  141. })
  142. const selectedTheme = localStorage.getItem('theme')
  143. if (selectedTheme && selectedTheme !== 'undefined') {
  144. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  145. }
  146. }
  147. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  148. window.addEventListener('load', () => {
  149. let hasDarkRules = false
  150. for (const styleSheet of Array.from(document.styleSheets)) {
  151. let mediaRules = []
  152. for (const cssRule of styleSheet.cssRules) {
  153. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  154. continue
  155. }
  156. // WARNING: Safari does not have/supports `conditionText`.
  157. if (cssRule.conditionText) {
  158. if (cssRule.conditionText !== prefersColorSchemeDark) {
  159. continue
  160. }
  161. } else {
  162. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  163. continue
  164. }
  165. }
  166. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  167. }
  168. // WARNING: do not try to insert a Rule to a styleSheet you are
  169. // currently iterating on, otherwise the browser will be stuck
  170. // in a infinite loop…
  171. for (const mediaRule of mediaRules) {
  172. styleSheet.insertRule(mediaRule.cssText)
  173. hasDarkRules = true
  174. }
  175. }
  176. if (hasDarkRules) {
  177. loadThemeForm('#theme-selector')
  178. }
  179. })
  180. </script>
  181. </body>
  182. </html>