{"id":13856752,"url":"https://github.com/lukesmurray/markdown-anki-decks","last_synced_at":"2025-10-10T03:03:53.676Z","repository":{"id":37393330,"uuid":"323956286","full_name":"lukesmurray/markdown-anki-decks","owner":"lukesmurray","description":"Tool for converting markdown files into anki decks","archived":false,"fork":false,"pushed_at":"2023-07-20T11:29:59.000Z","size":485,"stargazers_count":139,"open_issues_count":14,"forks_count":13,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-09-20T07:53:42.122Z","etag":null,"topics":["anki","markdown"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lukesmurray.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-12-23T16:54:53.000Z","updated_at":"2025-09-07T14:10:24.000Z","dependencies_parsed_at":"2024-09-20T14:41:09.593Z","dependency_job_id":null,"html_url":"https://github.com/lukesmurray/markdown-anki-decks","commit_stats":{"total_commits":47,"total_committers":3,"mean_commits":"15.666666666666666","dds":0.06382978723404253,"last_synced_commit":"4a29b7b5f898472672b3bc4d235a7c0953f5a823"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/lukesmurray/markdown-anki-decks","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukesmurray%2Fmarkdown-anki-decks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukesmurray%2Fmarkdown-anki-decks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukesmurray%2Fmarkdown-anki-decks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukesmurray%2Fmarkdown-anki-decks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lukesmurray","download_url":"https://codeload.github.com/lukesmurray/markdown-anki-decks/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lukesmurray%2Fmarkdown-anki-decks/sbom","scorecard":{"id":604841,"data":{"date":"2025-08-11","repo":{"name":"github.com/lukesmurray/markdown-anki-decks","commit":"4a29b7b5f898472672b3bc4d235a7c0953f5a823"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 1/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 3 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"10 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2024-48 / GHSA-fj7x-q9j7-g6q6","Warn: Project is vulnerable to: GHSA-cpwx-vrp4-4pq7","Warn: Project is vulnerable to: GHSA-gmj6-6f8f-6699","Warn: Project is vulnerable to: GHSA-h5c8-rqwp-cp95","Warn: Project is vulnerable to: GHSA-h75v-3vvj-5mfj","Warn: Project is vulnerable to: GHSA-q2x7-8rv6-6q7h","Warn: Project is vulnerable to: PYSEC-2022-42969","Warn: Project is vulnerable to: PYSEC-2023-117 / GHSA-mrwq-x4v8-fh7p","Warn: Project is vulnerable to: PYSEC-2024-187 / GHSA-rqc4-2hc7-8c8v","Warn: Project is vulnerable to: GHSA-jfmj-5v4g-7637"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T01:20:22.624Z","repository_id":37393330,"created_at":"2025-08-21T01:20:22.624Z","updated_at":"2025-08-21T01:20:22.624Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002537,"owners_count":26083417,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anki","markdown"],"created_at":"2024-08-05T03:01:11.789Z","updated_at":"2025-10-10T03:03:53.659Z","avatar_url":"https://github.com/lukesmurray.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# markdown-anki-decks\n\n![PyPI](https://img.shields.io/pypi/v/markdown-anki-decks)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/markdown-anki-decks)\n![PyPI - License](https://img.shields.io/pypi/l/markdown-anki-decks)\n\nMarkdown anki decks is a simple program to convert markdown files into anki decks.\n\n```md\n# The h1 tag is the deck title\n\n## The h2 tags are the questions\n\nThe markdown content between h2 tags are the answers.\n```\n\nMarkdown anki decks uses the question to uniquely identify the card.\nYou can change the card contents without losing your progress on the card.\nMarkdown anki decks can be reimported without creating duplicates.\n\n## Installation\n\nMake sure you have a python version of 3.7 or greater installed.\n\n`pip install markdown-anki-decks`\n\nThis will install the `mdankideck` cli tool.\n\n## Tutorial\n\nMarkdown anki decks converts all markdown files in an input directory to `apkg` files.\nThe `apkg` files are stored in an output directory.\n\n1. Create the input directory `mkdir input`.\n2. Create the output directory `mkdir output`.\n3. Create a markdown file in the input directory.\n\n   ```md\n   \u003c!-- input/deck.md --\u003e\n\n   # Deck Title\n\n   ## Card Title\n\n   card contents.\n\n   ## Second Card Title\n\n   card contents 2.\n   ```\n\n4. Run `mdankideck input output` to convert the markdown files in the input directory to `apkg` files in the output directory.\n5. Import `apkg` files as decks into anki.\n\n## Usage\n\n`mdankideck` comes with built in documentation. Run `mdankideck --help` to see it.\n\n```\nUsage: mdankideck [OPTIONS] INPUT_DIR OUTPUT_DIR\n\nArguments:\n  INPUT_DIR   The input directory. Contains markdown files which will be\n              converted to anki decks.  [required]\n\n  OUTPUT_DIR  The output directory. Anki .apkg files will be written here.\n              [required]\n\n\nOptions:\n  --sync                Whether or not to synchronize the output with anki\n                        using anki connect.  [default: False]\n\n  --prefix TEXT         Can be used to make your markdown decks part of a\n                        single subdeck. Anki uses `::` to indicate sub decks.\n                        `markdown-decks::` could be used to make all generated\n                        decks part of a single root deck `markdown-decks`\n                        [default: ]\n\n  --delete              Whether to delete cards from anki during sync. If sync\n                        is false this has no effect.  [default: False]\n\n  --cloze               Whether to support cloze syntax  [default: False]\n  --version             Show version information  [default: False]\n  --install-completion  Install completion for the current shell.\n  --show-completion     Show completion for the current shell, to copy it or\n                        customize the installation.\n\n  --help                Show this message and exit.\n```\n\n### Syncing\n\nMarkdown anki decks can use [AnkiConnect](https://ankiweb.net/shared/info/2055492159) to sync the created decks immediately to anki.\nFirst you need to install AnkiConnect as an add on in Anki.\nThen you need to call `mdankideck` with the `--sync` flag.\nBy default if you delete a question in markdown we do not delete the question in Anki during sync.\nHowever you can delete missing questions in Anki during sync by calling `mdankideck` with the `--delete` flag.\n\nIf you see an error message `Unable to reach anki connect. Make sure anki is running and the Anki Connect addon is installed.`, make sure you have installed anki connect and that you are running anki.\n\n```sh\n# convert markdown file in the input directory to apkg files in the output\n# directory and sync the decks to anki using anki connect\nmdankideck input output --sync\n\n\n# convert input to output with an empty deck prefix (default)\n# and delete cards from anki if the question is deleted or changed in markdown\nmdankideck input output --sync --delete\n```\n\n### Subdecks\n\nYou can use the Deck title prefix option to make all your markdown decks part of a single subdeck.\nAnki automatically creates subdecks based on deck names.\n\n\u003e Decks can contain other decks, which allows you to organize decks into a tree. Anki uses “::” to show different levels. A deck called “Chinese::Hanzi” refers to a “Hanzi” deck, which is part of a “Chinese” deck. If you select “Hanzi” then only the Hanzi cards will be shown; if you select “Chinese” then all Chinese cards, including Hanzi cards, will be shown. [Source](https://docs.ankiweb.net/#/getting-started?id=decks)\n\nI use a prefix `md::` to store all my markdown decks in a subdeck called `md`.\n\n```sh\n# sync all cards to anki with a root deck `md`\nmdankideck input output True \"md::\" True\n```\n\n### Images\n\nMarkdown anki decks support images which are stored in the same folder as the markdown file they are referenced by.\n\n`![my-image](image.jpg)` will work because it is in the same folder as the markdown file.\n\n`![my-image](./images/image.jpg)` will not work because it is in a different folder than the markdown file.\n\nAll images must have unique filenames even if they are stored in different folders.\n\nThese are limitations of anki not Markdown anki decks.\n\n### Sounds\n\nUse the syntax `[sound:sound-file.mp3]` to add a sound to your card.\n\nMarkdown anki decks support sounds which are stored in the same folder as the markdown file they are referenced by.\n\n`[sound:sound-file.mp3]` will work because it is in the same folder as the markdown file.\n\n`[sound:./sounds/sound-file.mp3]` will not work because it is in a different folder than the markdown file.\n\nAll sounds must have unique filenames even if they are stored in different folders.\n\nThese are limitations of anki not Markdown anki decks.\n\n### Styling Cards\n\nThe cards are styled with minimal css [markdown.css](markdown_anki_decks/styles/markdown.css).\nSyntax highlighting is provided via [pygments](https://github.com/pygments/pygments).\nThe syntax highlighting uses the pygments default theme.\n\n### Clozes (experimental)\n\nmdankideck supports close syntax in questions if you call `mdankideck` with the `--cloze` flag. Clozes can be specified in the question using the template `{{c#::answer::hint}}` where `#` is a number and answer and hint are words or multiple words. The hint is optional so you can also write `{{c#::answer}}`. Clozes with the same `c#` will be hidden together.\n\nThe following markdown would create two cloze cards.\n\n```md\n## The alphabet starts with {{c1::a}} {{c2::b}} {{c1::a}}\n```\n\n`The alphabet starts with [...] b [...]`\n\nand\n\n`The alphabet starts with a [...] c`\n\nThe `c1` clozes are blocked out together.\n\n#### Custom Styling\n\nYou can add custom styling using yaml frontmatter. The css key takes a path to a css file as a string or an array of paths to multiple css files.\n\n```yaml\n---\ncss: custom-styles.css\n---\n```\n\n```yaml\n---\ncss: [\"one.css\", \"two.css\", \"three.css\", \"four.css\"]\n---\n```\n\nThe paths to the css files are assumed to be relative to the markdown file.\nCards have the following html structure. During processing markdown anki decks wraps your answer in a section tag. So the answer will be `\u003csection\u003e{{Answer}}\u003c/section\u003e`. This extra wrapping step is performed because answers can consist of multiple sibling html elements and we need to provide a single element to Anki.\n\n```html\n\u003c!-- the question card --\u003e\n\u003cdiv class=\"card\"\u003e\n  \u003cdiv class=\"question\"\u003e{{Question}}\u003c/div\u003e\n\u003c/div\u003e\n\n\u003c!-- the answer card --\u003e\n\u003cdiv class=\"card\"\u003e\n  \u003cdiv class=\"question\"\u003e{{Question}}\u003c/div\u003e\n  \u003chr /\u003e\n  \u003cdiv class=\"answer\"\u003e{{Answer}}\u003c/div\u003e\n\u003c/div\u003e\n```\n\nYou can style cards in general using the `.card` selector and you can style questions and answers using the `.question` and `.answer` selector.\n\n```css\n/* apply red background to all cards */\n.card {\n  background: red;\n}\n```\n\n### Mathjax\n\n[Anki supports mathjax out of the box](https://docs.ankiweb.net/#/math?id=mathjax). However you need to be careful with how markdown escapes characters. The following code snippet will not render as mathjax when you convert it because markdown will assume you are trying to escape the parentheses and will render the text `(\\sqrt{x})`\n\n```md\n\\(\\sqrt{x}\\)\n```\n\nYou need to escape the backslashes by using double backslashes.\nThe following snippet will work to render math using mathjax.\n\n```md\n\u003c!-- this will render inline math --\u003e\n\n\\\\(\\sqrt{x}\\\\)\n\n\u003c!-- this will render block math --\u003e\n\n\\\\[\\sqrt{x}\\\\]\n```\n\n### Markdown Features\n\nMost commonly used markdown features should work without any issues.\nIf there is a Markdown feature you want to use and it is supported by one of the [official extensions for python-markdown](https://python-markdown.github.io/extensions/#officially-supported-extensions) there is good chance it can be added to the project.\nCheck out `cli.py` to see the list of currently enabled extensions. (Search for `extensions=`)\n\n#### Multiline questions\n\nYou may want to render complex questions which span multiple lines.\nYou can use the fact that any `h2` element is assumed to be a question combined with the fact that the markdown converter supports embedding markdown in html if you use the special attribute `markdown=\"block\"` on the html element. It's much easier to see with an example.\n\n```md\n\u003ch2 markdown=\"block\"\u003e\n- this question\n- is actually\n- a markdown list\n\nYou can even add math to your questions \\\\(\\sqrt{2}\\\\)\n\n\u003c/h2\u003e\n```\n\nAdditionally you may not want `h2` styling for your multiline question. In that case you can add the `data-question` attribute to any tag to make it a question.\n\n```md\n\u003cdiv data-question markdown=\"block\"\u003e\n\nThis is a question which does not have h2 formatting!\n\n- it can contain nested markdown such as `code`\n\n\u003c/div\u003e\n```\n\nFor more information [see the `md_in_html` documentation](https://github.com/Python-Markdown/markdown/blob/master/docs/extensions/md_in_html.md)\n\n## Limitations\n\nMarkdown anki decks makes some assumptions to enable syncing.\nCards are uniquely identified by their deck name and question.\nIf you change the deck name or the question you will lose the card history.\nAll deck names must be unique.\nAll questions in a single deck must be unique.\nIdentical questions in separate decks are ok.\n\n## Design\n\nThe markdown files are parsed with [python-markdown](https://pypi.org/project/Markdown/). The resulting html is then parsed with [beautifulsoup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/).\nFinally the cards are created with [genanki](https://github.com/kerrickstaley/genanki).\nThe cli is implemented using [typer](https://github.com/tiangolo/typer) and the program is packaged using [poetry](https://github.com/python-poetry/poetry).\n\n## Contributing\n\nHappy to discuss additional features if you open up an issue.\n\nWe use commitizen for commits.\nRun `poetry run cz commit` to make a commit.\n\nRun `poetry run mdankideck --prefix \"test::\" --sync --cloze testData/input testData/output` to convert the test data into decks.\n\nRun `poetry run pygmentize -S default -f html -a .codehilite \u003e ./markdown_anki_decks/styles/pygments.css` to create a pygments stylesheet.\nRun `poetry run pygmentize -S monokai -f html -a \".nightMode .codehilite\" \u003e ./markdown_anki_decks/styles/pygments-dark.css` to create the dark mode stylesheet.\nThe `-S` flag is used to specify the style. Run `poetry run pygmentize -L style` to list the styles pygmentize can use. Replace `default` with any of the styles to use a different style.\n\nCurrently use `default` for light mode and `monokai` for dark mode.\n\n### Releases\n\nRun `poetry run cz bump --check-consistency` to update the changelog and create a tag.\n\nRun `poetry publish --build` to publish the pack to pypi.\n\n`git push --tags \u0026\u0026 git push` to update github.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukesmurray%2Fmarkdown-anki-decks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flukesmurray%2Fmarkdown-anki-decks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flukesmurray%2Fmarkdown-anki-decks/lists"}