diff --git a/.github/manual_lint.js b/.github/manual_lint.js new file mode 100644 index 000000000..37846da --- /dev/null +++ b/.github/manual_lint.js @@ -0,0 +1,95 @@ +const util = require("util"); +const glob = util.promisify(require('glob')); +const fs = require("fs").promises; +const path = require('path'); + + +async function main() { + var errors = []; + var directories = await glob(__dirname + '../../dishes/**/*.md'); + + for (var filePath of directories) { + var data = await fs.readFile(filePath, 'utf8'); + var filename = path.parse(filePath).base.replace(".md",""); + + dataLines = data.split('\n').map(t => t.trim()); + titles = dataLines.filter(t => t.startsWith('#')); + secondTitles = titles.filter(t => t.startsWith('## ')); + + if (dataLines.filter(line => line.includes('勺')).length > + dataLines.filter(line => line.includes('勺子')).length + + dataLines.filter(line => line.includes('炒勺')).length + + dataLines.filter(line => line.includes('漏勺')).length + + dataLines.filter(line => line.includes('吧勺')).length) { + errors.push(`文件 ${filePath} 不符合仓库的规范!勺 不是一个精准的单位!`); + } + if (dataLines.filter(line => line.includes(' 杯')).length > + dataLines.filter(line => line.includes('杯子')).length) { + errors.push(`文件 ${filePath} 不符合仓库的规范!杯 不是一个精准的单位!`); + } + if (dataLines.filter(line => line.includes('适量')).length > 0) { + errors.push(`文件 ${filePath} 不符合仓库的规范!适量 不是一个精准的描述!请给出克 g 或毫升 ml。`); + } + if (dataLines.filter(line => line.includes('每人')).length + dataLines.filter(line => line.includes('人数')).length > 0) { + errors.push(`文件 ${filePath} 不符合仓库的规范!请基于每道菜\\每份为基准。不要基于人数。人数是一个可能会导致在应用中发生问题的单位。如果需要面向大量的人食用,请标明一个人需要几份。`); + } + if ( + dataLines.filter(line => line.includes('份数')).length > 0 && + ( + dataLines.filter(line => line.includes('总量')).length == 0 || + dataLines.filter(line => line.includes('每次制作前需要确定计划做几份。一份正好够')).length == 0 + ) + ) { + errors.push(`文件 ${filePath} 不符合仓库的规范!它使用份数作为基础,这种情况下一般是一次制作,制作多份的情况。请标明:总量 并写明 '每次制作前需要确定计划做几份。一份正好够 几 个人食用。'。`); + } + if (dataLines.filter(line => line.includes('min')).length > 0) { + errors.push(`文件 ${filePath} 不符合仓库的规范!min 这个词汇有多重含义。建议改成中文"分钟"。`); + } + if (dataLines.filter(line => line.includes('左右')).length > 0) { + errors.push(`文件 ${filePath} 不符合仓库的规范!左右 不是一个能够明确定量的标准! 如果是在描述一个模糊物体的特征,请使用 '大约'。例如:鸡(大约1kg)`); + } + if (dataLines.filter(line => line.includes('少许')).length > 0) { + errors.push(`文件 ${filePath} 不符合仓库的规范!少许 不是一个精准的描述!请给出克 g 或毫升 ml。`); + } + if (dataLines.filter(line => line.includes('你')).length + dataLines.filter(line => line.includes('我')).length > 0) { + errors.push(`文件 ${filePath} 不符合仓库的规范!请不要出现人称代词。`); + } + if (titles[0].trim() != "# " + filename + "的做法") { + errors.push(`文件 ${filePath} 不符合仓库的规范!它的大标题应该是: ${"# " + filename + "的做法"}! 而它现在是 ${titles[0].trim()}!`); + continue; + } + if (secondTitles.length != 4) { + errors.push(`文件 ${filePath} 不符合仓库的规范!它并不是四个标题的格式。请从示例菜模板中创建菜谱!请不要破坏模板的格式!`); + continue; + } + if (secondTitles[0].trim() != "## 必备原料和工具") { + errors.push(`文件 ${filePath} 不符合仓库的规范!第一个标题不是 必备原料和工具!`); + } + if (secondTitles[1].trim() != "## 计算") { + errors.push(`文件 ${filePath} 不符合仓库的规范!第二个标题不是 计算!`); + } + if (secondTitles[2].trim() != "## 操作") { + errors.push(`文件 ${filePath} 不符合仓库的规范!第三个标题不是 操作`); + } + if (secondTitles[3].trim() != "## 附加内容") { + errors.push(`文件 ${filePath} 不符合仓库的规范!第四个标题不是 附加内容`); + } + + var mustHave = '如果您遵循本指南的制作流程而发现有问题或可以改进的流程,请提出 Issue 或 Pull request 。'; + var mustHaveIndex = dataLines.indexOf(mustHave); + if (mustHaveIndex < 0) { + errors.push(`文件 ${filePath} 不符合仓库的规范! 它没有包含必需的附加内容!,需要在最后一行添加模板中的【如果您遵循本指南的制作流程而发现有……】`); + } + } + + if (errors.length > 0) { + for (var error of errors) { + console.error(error + "\n"); + } + + var message = `Found ${errors.length} errors! Please fix!`; + throw new Error(message); + } +} + +main(); \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..3fb2835 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## 注意 + +Pull Request 提交后,预计 1 分钟内将会得到自动化格式检查的结果。只有通过自动化检查的 Pull Request 才会被人工审核。 + +- [ ] 请确保此 Pull Request 能够通过自动化代码检查。 + +## 签署 + +必须签署下面的对话框才能开始审核。 + +### HowToCook 仓库采用了 [The Unlicense](https://unlicense.org/) 协议 + +菜谱在签入前,必须确保其可以**直接声明进入 "公共领域"(public domain)**。这意味着一旦合并后,任何人都**可以**自由复制,修改,发布,使用,编译,出售或以菜谱的形式或菜的形式分发,**无论**是出于**商业目的**还是**非商目的**,以及**任何**手段。 + +- [ ] 我确保了我的内容不涉及版权内容,并且授权它 The Unlicense 协议。 diff --git a/.github/readme-generate.js b/.github/readme-generate.js new file mode 100644 index 000000000..ceee167 --- /dev/null +++ b/.github/readme-generate.js @@ -0,0 +1,163 @@ +const { readdir, writeFile, stat } = require('fs/promises'); +const fs = require('fs').promises; + +const README_PATH = './README.md'; + +const MKDOCS_PATH = 'mkdocs.yml'; + +const ignorePaths = ['.git', 'README.md', 'node_modules', 'CONTRIBUTING.md', '.github']; + +const categories = { + vegetable_dish: { + title: '素菜', + readme: '', + mkdocs: '', + }, + meat_dish: { + title: '荤菜', + readme: '', + mkdocs: '', + }, + aquatic: { + title: '水产', + readme: '', + mkdocs: '', + }, + breakfast: { + title: '早餐', + readme: '', + mkdocs: '', + }, + staple: { + title: '主食', + readme: '', + mkdocs: '', + }, + 'semi-finished': { + title: '半成品加工', + readme: '', + mkdocs: '', + }, + soup: { + title: '汤与粥', + readme: '', + mkdocs: '', + }, + drink: { + title: '饮料', + readme: '', + mkdocs: '', + }, + condiment: { + title: '酱料和其它材料', + readme: '', + mkdocs: '', + }, + dessert: { + title: '甜品', + readme: '', + mkdocs: '', + }, +}; + +async function main() { + try { + let README_BEFORE = (README_MAIN = README_AFTER = ''); + let MKDOCS_BEFORE = (MKDOCS_MAIN = MKDOCS_AFTER = ''); + const markdownObj = await getAllMarkdown('.'); + for (const markdown of markdownObj) { + if (markdown.path.includes('tips/advanced')) { + README_AFTER += inlineReadmeTemplate(markdown.file, markdown.path); + MKDOCS_AFTER += inlineMkdocsTemplate(markdown.file, markdown.path); + continue; + } + + if (markdown.path.includes('tips')) { + README_BEFORE += inlineReadmeTemplate(markdown.file, markdown.path); + MKDOCS_BEFORE += inlineMkdocsTemplate(markdown.file, markdown.path); + continue; + } + + for (const category of Object.keys(categories)) { + if (!markdown.path.includes(category)) continue; + categories[category].readme += inlineReadmeTemplate(markdown.file, markdown.path); + categories[category].mkdocs += inlineMkdocsTemplate( + markdown.file, + markdown.path, + true, + ); + } + } + + for (const category of Object.values(categories)) { + README_MAIN += categoryReadmeTemplate(category.title, category.readme); + MKDOCS_MAIN += categoryMkdocsTemplate(category.title, category.mkdocs); + } + + const MKDOCS_TEMPLATE = await fs.readFile("./.github/templates/mkdocs_template.yml", "utf-8"); + const README_TEMPLATE = await fs.readFile("./.github/templates/readme_template.md", "utf-8"); + + await writeFile( + README_PATH, + README_TEMPLATE + .replace('{{before}}', README_BEFORE.trim()) + .replace('{{main}}', README_MAIN.trim()) + .replace('{{after}}', README_AFTER.trim()), + ); + + + await writeFile( + MKDOCS_PATH, + MKDOCS_TEMPLATE + .replace('{{before}}', MKDOCS_BEFORE) + .replace('{{main}}', MKDOCS_MAIN) + .replace('{{after}}', MKDOCS_AFTER), + ); + } catch (error) { + console.error(error); + } +} + +async function getAllMarkdown(path) { + const paths = []; + const files = await readdir(path); + // chinese alphabetic order + files.sort((a, b) => a.localeCompare(b, 'zh-CN')); + + // mtime order + // files.sort(async (a, b) => { + // const aStat = await stat(`${path}/${a}`); + // const bStat = await stat(`${path}/${b}`); + // return aStat.mtime - bStat.mtime; + // }); + for (const file of files) { + const filePath = `${path}/${file}`; + if (ignorePaths.includes(file)) continue; + const fileStat = await stat(filePath); + if (fileStat.isFile() && file.endsWith('.md')) { + paths.push({ path, file }); + } else if (fileStat.isDirectory()) { + const subFiles = await getAllMarkdown(filePath); + paths.push(...subFiles); + } + } + return paths; +} + +function inlineReadmeTemplate(file, path) { + return `- [${file.replace('.md', '')}](${path}/${file})\n`; +} + +function categoryReadmeTemplate(title, inlineStr) { + return `\n### ${title}\n\n${inlineStr}`; +} + +function inlineMkdocsTemplate(file, path, isDish = false) { + return `${' '.repeat(isDish ? 10 : 6)}- ${file.replace('.md', '')}: ${path}/${file}\n`; +} + +function categoryMkdocsTemplate(title, inlineStr) { + return `\n${' '.repeat(6)}- ${title}:\n${inlineStr}`; +} + +main(); diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..93de41e --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,28 @@ +# Configuration for probot-stale - https://github.com/probot/stale +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 30 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - "Type: Bug" +# Label to use when marking an issue as stale +staleLabel: "Resolution: Stale" +issues: + # Comment to post when marking an issue as stale. + markComment: > + This issue has been automatically marked as stale. + **If this issue is still affecting you, please leave any comment** (for example, "bump"), and we'll keep it open. + We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! + # Comment to post when closing a stale issue. + closeComment: > + Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you! +pulls: + # Comment to post when marking a pull request as stale. + markComment: > + This pull request has been automatically marked as stale. + **If this pull request is still relevant, please leave any comment** (for example, "bump"), and we'll keep it open. + We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated. + # Comment to post when closing a stale pull request. + closeComment: > + Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you! diff --git a/.github/templates/mkdocs_template.yml b/.github/templates/mkdocs_template.yml new file mode 100644 index 000000000..d80a27d --- /dev/null +++ b/.github/templates/mkdocs_template.yml @@ -0,0 +1,86 @@ +site_name: How To Cook + +# Repository +repo_name: Anduin2017/HowToCook +repo_url: https://github.com/Anduin2017/HowToCook +edit_uri: "" + +use_directory_urls: true +docs_dir: . +theme: + name: material + language: zh + features: + - content.code.annotate + # - content.tabs.link + # - header.autohide + # - navigation.expand + # - navigation.indexes + - navigation.instant + - navigation.sections + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + - navigation.footer + - navigation.tracking + - search.highlight + - search.share + - search.suggest + - toc.follow + # - toc.integrate + search_index_only: true + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material//weather-sunny + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/weather-night + name: Switch to light mode + + icon: + admonition: + note: octicons/tag-16 + abstract: octicons/checklist-16 + info: octicons/info-16 + tip: octicons/squirrel-16 + success: octicons/check-16 + question: octicons/question-16 + warning: octicons/alert-16 + failure: octicons/x-circle-16 + danger: octicons/zap-16 + bug: octicons/bug-16 + example: octicons/beaker-16 + quote: octicons/quote-16 + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - abbr + - pymdownx.snippets + - def_list + - pymdownx.tasklist: + custom_checkbox: true + - attr_list + +plugins: + - same-dir + - search + - minify: + minify_html: true + +nav: + - README.md + - 做菜之前: +{{before}} + - 菜谱: + - 按种类: # 只有两层section以上才能出现navigation expansion https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/#navigation-sections +{{main}} + - 进阶知识学习: +{{after}} + - CONTRIBUTING.md + - CODE_OF_CONDUCT.md diff --git a/.github/templates/readme_template.md b/.github/templates/readme_template.md new file mode 100644 index 000000000..17bce84 --- /dev/null +++ b/.github/templates/readme_template.md @@ -0,0 +1,34 @@ +# 程序员做饭指南 + +[![build](https://github.com/Anduin2017/HowToCook/actions/workflows/build.yml/badge.svg)](https://github.com/Anduin2017/HowToCook/actions/workflows/build.yml) +[![License](https://img.shields.io/github/license/Anduin2017/HowToCook)](./LICENSE) +[![GitHub contributors](https://img.shields.io/github/contributors/Anduin2017/HowToCook)](https://github.com/Anduin2017/HowToCook/graphs/contributors) +[![npm](https://img.shields.io/npm/v/how-to-cook)](https://www.npmjs.com/package/how-to-cook) + +最近在家隔离,出不了门。只能宅在家做饭了。作为程序员,我偶尔在网上找找菜谱和做法。但是这些菜谱往往写法千奇百怪,经常中间莫名出来一些材料。对于习惯了形式语言的程序员来说极其不友好。 + +所以,我计划自己搜寻菜谱并结合实际做菜的经验,准备用更清晰精准的描述来整理常见菜的做法,以方便程序员在家做饭。 + +同样,我希望它是一个由社区驱动和维护的开源项目,使更多人能够一起做一个有趣的仓库。所以非常欢迎大家贡献它~ + +## 如何贡献 + +针对发现的问题,直接修改并提交 Pull request 即可。 + +在写新菜谱时,请复制并修改已有的菜谱模板: [示例菜](https://github.com/Anduin2017/HowToCook/blob/master/dishes/template/%E7%A4%BA%E4%BE%8B%E8%8F%9C/%E7%A4%BA%E4%BE%8B%E8%8F%9C.md?plain=1)。 + +## 做菜之前 + +{{before}} + +## 菜谱 + +### 家常菜 + +{{main}} + +## 进阶知识学习 + +如果你已经做了许多上面的菜,对于厨艺已经入门,并且想学习更加高深的烹饪技巧,请继续阅读下面的内容: + +{{after}} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..34dd671 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,50 @@ +name: build + +on: + push: + branches: [ master ] + workflow_dispatch: + +jobs: + Rebuild-everything: + runs-on: ubuntu-latest + steps: + # Checkout, install tools.. + - uses: actions/checkout@v2 + with: + token: ${{ secrets.PAT }} + - uses: actions/setup-node@v2 + with: + node-version: '16' + cache: 'npm' + - name: Install packages + run: sudo gem install mdl + # Generate Readme, mkdocs. + - run: node ./.github/readme-generate.js + # Lint issues first. (Without node_modules) + - name: Lint markdown files + run: mdl . -r ~MD036,~MD024,~MD004,~MD029,~MD013,~MD007 + - run: pip install -r requirements.txt + - run: mkdocs build --strict + # Do textlint fix. + - run: npm install + - run: ./node_modules/.bin/textlint . --fix + - run: rm ./node_modules -rvf + # Save files. + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: '[ci skip] Automatic file changes/fix' + branch: 'master' + file_pattern: ':/*.*' + commit_user_name: github-actions[bot] + commit_user_email: github-actions[bot]@users.noreply.github.com + commit_author: github-actions[bot] + # Build docs + - run: echo cook.aiurs.co > CNAME + - run: mkdir docs && echo cook.aiurs.co > docs/CNAME + - uses: mhausenblas/mkdocs-deploy-gh-pages@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CUSTOM_DOMAIN: cook.aiurs.co + CONFIG_FILE: mkdocs.yml + REQUIREMENTS: requirements.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..3b7596b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: Continuous Integration + +on: + pull_request: + branches: [ master ] + +jobs: + markdown-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '16' + cache: 'npm' + - name: Install packages + run: sudo gem install mdl + - name: Lint markdown files + run: mdl . -r ~MD036,~MD024,~MD004,~MD029,~MD013,~MD007 + - run: pip install -r requirements.txt + - run: mkdocs build --strict + - run: npm install + - run: node .github/manual_lint.js + # Suppress 036 Emphasis used instead of a header + # Suppress 024 Multiple headers with the same content diff --git a/README.md b/README.md index 79eca36..076d379 100644 --- a/README.md +++ b/README.md @@ -320,6 +320,7 @@ - [反沙芋头](./dishes/dessert/反沙芋头/反沙芋头.md) - [咖啡椰奶冻](./dishes/dessert/咖啡椰奶冻/咖啡椰奶冻.md) - [烤蛋挞](./dishes/dessert/烤蛋挞/烤蛋挞.md) +- [玛格丽特饼干](./dishes/dessert/玛格丽特饼干/玛格丽特饼干.md) - [魔芋蛋糕](./dishes/dessert/魔芋蛋糕/魔芋蛋糕.md) - [戚风蛋糕](./dishes/dessert/戚风蛋糕/戚风蛋糕.md) - [提拉米苏](./dishes/dessert/提拉米苏/提拉米苏.md) diff --git a/dishes/condiment/炸串酱料.md b/dishes/condiment/炸串酱料.md index 0e38f3d..399efde 100644 --- a/dishes/condiment/炸串酱料.md +++ b/dishes/condiment/炸串酱料.md @@ -1,4 +1,3 @@ - # 炸串酱料的做法 炸串酱料,号称淋袜子都好吃,新手友好,预计用时 10 分钟。 diff --git a/dishes/dessert/玛格丽特饼干/玛格丽特饼干.jpg b/dishes/dessert/玛格丽特饼干/玛格丽特饼干.jpg new file mode 100644 index 000000000..5d21018 Binary files /dev/null and b/dishes/dessert/玛格丽特饼干/玛格丽特饼干.jpg differ diff --git a/dishes/dessert/玛格丽特饼干/玛格丽特饼干.md b/dishes/dessert/玛格丽特饼干/玛格丽特饼干.md new file mode 100644 index 000000000..b84fafd --- /dev/null +++ b/dishes/dessert/玛格丽特饼干/玛格丽特饼干.md @@ -0,0 +1,48 @@ +# 玛格丽特饼干的做法 + +![玛格丽特成品](./玛格丽特饼干.jpg) + +玛格丽特饼干通常作为下午茶点心或伴随热饮享用,是一种经典而受欢迎的点心。它们的酥脆质地和丰富的黄油味道使它们成为许多人喜爱的饼干之一。 + +## 必备原料和工具 + +- 熟蛋黄 +- 黄油 +- 白砂糖 +- 盐 +- 低筋面粉 +- 玉米淀粉 +- 烤箱 + +## 计算 + +鉴于这种小甜点的高热量,每次制作前需要确定计划做几份。一份正好够一个人食用。 + +每份: + +- 熟蛋黄 1 个 +- 黄油 50 克 +- 白砂糖 20 克 +- 盐 1 克 +- 低筋面粉 50 克 +- 玉米淀粉 50 克 + +## 操作 + +- 黄油隔热水融化、将蛋黄磨碎备用。 +- 在融化的黄油中添加糖、盐、以及碾碎的鸡蛋黄,搅拌均匀 +- 加入低筋面粉与玉米淀粉,揉成面团 +- 将面团均匀分割成大约 8 克重的小面团,然后将它们搓成球状。 +- 使用大拇指轻压在每个小面团上,以形成裂纹。 +- 预热烤箱至 150℃,将小面团放入烤箱中,烘烤 20 分钟。 +- 微微放凉即可食用 + +## 附加内容 + +- 可根据个人口味微调盐与糖的比例,如果喜欢其他风味可以把每份的 3 克玉米淀粉换成可可粉或抹茶粉。 +- 如果条件允许可以把蛋黄、低筋面粉等食材过筛,口感会更好。 +- 介于材料获取难度选择了白砂糖,如果条件允许可以换成糖粉 30 克。 +- 如果没有烤箱可使用微波炉、空气炸锅等,微波炉为高火 2-3 分钟,空气炸锅为 150℃20 分钟 +- 参考资料:[第一百种可邮寄的小饼干 酥酥糯糯入口即化~-哔哩哔哩](https://b23.tv/NZCsV0x) + +如果您遵循本指南的制作流程而发现有问题或可以改进的流程,请提出 Issue 或 Pull request 。 diff --git a/mkdocs.yml b/mkdocs.yml index 9940c51..067d483 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -366,6 +366,7 @@ nav: - 反沙芋头: ./dishes/dessert/反沙芋头/反沙芋头.md - 咖啡椰奶冻: ./dishes/dessert/咖啡椰奶冻/咖啡椰奶冻.md - 烤蛋挞: ./dishes/dessert/烤蛋挞/烤蛋挞.md + - 玛格丽特饼干: ./dishes/dessert/玛格丽特饼干/玛格丽特饼干.md - 魔芋蛋糕: ./dishes/dessert/魔芋蛋糕/魔芋蛋糕.md - 戚风蛋糕: ./dishes/dessert/戚风蛋糕/戚风蛋糕.md - 提拉米苏: ./dishes/dessert/提拉米苏/提拉米苏.md