├── .eslintrc.json ├── .gitignore ├── CHANGELOG.md ├── README.md ├── __mocks__ ├── .eslintrc.json └── anki-apkg-export.js ├── __tests__ ├── .eslintrc.json ├── configs │ └── index.test.js ├── file_serializer.test.js ├── models │ ├── card.test.js │ ├── deck.test.js │ └── media.test.js ├── parsers │ ├── card_parser.test.js │ ├── md_parser.test.js │ └── media_parser.test.js ├── transformer.test.js └── utils.test.js ├── jest.config.js ├── package-lock.json ├── package.json ├── resources ├── dark.css ├── default.css ├── highlight.js └── prism.js ├── samples ├── complex.md ├── media.md ├── media_remote.md ├── resources │ ├── nodejs.png │ └── ruby_on_rails.png └── simple.md └── src ├── configs ├── index.js └── settings.js ├── file_serializer.js ├── index.js ├── models ├── card.js ├── deck.js ├── media.js └── template.js ├── parsers ├── base_parser.js ├── card_parser.js ├── md_parser.js └── media_parser.js ├── transformer.js └── utils.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true 6 | }, 7 | "extends": "airbnb-base", 8 | "globals": { 9 | "Atomics": "readonly", 10 | "SharedArrayBuffer": "readonly" 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2018 14 | }, 15 | "rules": { 16 | "class-methods-use-this": "off", 17 | "no-console": "off", 18 | "key-spacing": ["error", { "align": "colon" }], 19 | "no-await-in-loop": "off", 20 | "no-restricted-syntax": "off", 21 | "no-multi-spaces": ["error", { 22 | "exceptions": { 23 | "VariableDeclarator": true, 24 | "PropertyAssignment": true, 25 | "AssignmentExpression": true 26 | } 27 | }] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.apkg 2 | .vscode 3 | node_modules 4 | tmp 5 | .DS_Store 6 | coverage 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # MDAnki Changelog 2 | 3 | #### v1.1.0 - 2020.12.14 4 | * Add remote media loading ([#20](https://github.com/ashlinchak/mdanki/pull/20)) 5 | #### v1.0.2 - 2020.02.21 6 | * Prefer the explicit deck name over calculated ([#2](https://github.com/ashlinchak/mdanki/pull/2)) 7 | 8 | #### v1.0.1 - 2020.02.21 9 | * Add tests ([#1](https://github.com/ashlinchak/mdanki/pull/1)) 10 | * Fix minor bugs ([#1](https://github.com/ashlinchak/mdanki/pull/1)) 11 | 12 | #### v0.0.1 - 2020.02.11 13 | * Initial release 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MDAnki 2 | 3 | Converts Markdown file(s) to the Anki cards. 4 | 5 | - [MDAnki](#mdanki) 6 | - [Requirements](#requirements) 7 | - [Install](#install) 8 | - [Usage](#usage) 9 | - [Overriding default settings](#overriding-default-settings) 10 | - [Supported files](#supported-files) 11 | - [Cards](#cards) 12 | - [Tags](#tags) 13 | - [Code and syntax highlighting](#code-and-syntax-highlighting) 14 | - [Supported languages](#supported-languages) 15 | - [Images](#images) 16 | - [LaTeX](#latex) 17 | - [Memory limit](#memory-limit) 18 | - [License](#license) 19 | - [Changelog](#changelog) 20 | 21 | ## Requirements 22 | 23 | Node.js v10.0+ 24 | 25 | ## Install 26 | ```bash 27 | npm install -g mdanki 28 | ``` 29 | 30 | ## Usage 31 | 32 | Convert a single markdown file: 33 | 34 | ```bash 35 | mdanki library.md anki.apkg 36 | ``` 37 | 38 | Convert files from directory recursively: 39 | 40 | ```bash 41 | mdanki ./documents/library ./documents/anki.apkg 42 | ``` 43 | 44 | Using all available options: 45 | 46 | ```bash 47 | mdanki library.md anki.apkg --deck Library --config config.json 48 | ``` 49 | 50 | Import just generated `.apkg` file to Anki ("File" - "Import"). 51 | 52 | ## Overriding default settings 53 | 54 | To override [default settings](./src/configs/settings.js) use `--config` option: 55 | 56 | ```bash 57 | mdanki library.md anki.apkg --config faworite-settings.json 58 | ``` 59 | 60 | The JSON file, for example, would look like the following if you were to change 61 | the mdanki card template to the default that Anki has: 62 | 63 | ```json 64 | { 65 | "template": { 66 | "formats": { 67 | "question": "{{Front}}", 68 | "answer" : "{{FrontSide}}\n\n
\n\n{{Back}}", 69 | "css" : ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}" 70 | } 71 | } 72 | } 73 | ``` 74 | 75 | 76 | ## Supported files 77 | 78 | MDAnki supports `.md` and `.markdown` files. 79 | 80 | ## Cards 81 | 82 | By default, MDAnki splits cards by `## ` headline. For example, below markdown will generate 2 cards where headlines will be on the front side and its description - on the back. 83 | 84 | ``` 85 | ## What's the Markdown? 86 | 87 | Markdown is a lightweight markup language with plain-text-formatting syntax. 88 | Its design allows it to be converted to many output formats, 89 | but the original tool by the same name only supports HTML. 90 | 91 | ## Who created Markdown? 92 | 93 | John Gruber created the Markdown language in 2004 in collaboration with 94 | Aaron Swartz on the syntax. 95 | 96 | ``` 97 | 98 | If you want to have multiple lines on the card's front side - use `%` symbol for splitting front and back sides: 99 | 100 | ``` 101 | ## YAGNI 102 | 103 | Describe this acronym and why it's so important. 104 | 105 | % 106 | 107 | "You aren't gonna need it" (YAGNI) is a principle of extreme programming 108 | (XP) that states a programmer should not add functionality until deemed 109 | necessary. 110 | 111 | ``` 112 | 113 | When parsing only one markdown file, the title of the deck could be generated based on the top-level headline (`# `). 114 | 115 | ## Tags 116 | 117 | Cards can have tags in their markdown sources. For adding tags to cart it should follow some rules: 118 | * tags start from a new line 119 | * only one line with tags per card 120 | * a tag should be written in the link format 121 | * tag (link text) should start from `#` symbol 122 | 123 | MDAnki uses `'^\\[#(.*)\\]'` pattern for searching tags. This pattern could be overwritten by specifying custom settings. The source file in the tag link is optional. 124 | 125 | The below example will generate a card with 3 tags: _algorithms_, _OOP_, and _binary_tree_. 126 | 127 | ``` 128 | ## Binary tree 129 | 130 | In computer science, a binary tree is a tree data structure in which each node has at most two children, which are referred to as the left child and the right child. 131 | 132 | [#algorithms](./algorityms.md) [#OOP]() [#binary tree]() 133 | ``` 134 | 135 | ## Code and syntax highlighting 136 | 137 | Code blocks can be written with and without specifying a language name: 138 | 139 |
140 | ```java
141 | public static void main(String[] args) {
142 |   System.out.println("Hello, World!");
143 | }
144 | ```
145 | 
146 |
147 | ```
148 | echo "Hello, World!"
149 | ```
150 | 
151 | 152 | The last code block will be treated by MDAnki as Bash code. The default language can be configured by specifying `--config` with an appropriate **defaultLanguage** [setting](../src/configs/settings.js). 153 | 154 | **Note!** Creating a block without language name is not fully supported and should be eliminated in usage. Take a look at this: 155 | ```bash 156 | echo "Code block with language name" 157 | ``` 158 | ``` 159 | echo "Code block without language name" 160 | ``` 161 | 162 | ## Supported languages 163 | 164 | MDAnki supports code highlighting for these languages: 165 | 166 | > actionscript, applescript, aspnet, bash, basic, batch, c, coffeescript, cpp, csharp, d, dart, erlang, fsharp, go, graphql, groovy, handlebars, java, json, latex, less, livescript, lua, makefile, markdown, markup-templating, nginx, objectivec, pascal, perl, php, powershell, python, r, ruby, rust, sass, scheme, smalltalk, smarty, sql, stylus, swift, typescript, vim, yaml. 167 | 168 | 169 | ## Images 170 | 171 | You can use links to image files inside markdown, MDAnki will parse them and add those images to the import collection. It's allowed to use two styles for writing images: 172 | 173 | 1. Inline: 174 | ![alt text](samples/resources/nodejs.png "Node.js") 175 | 176 | 1. Reference: 177 | ![alt text][ROR] 178 | 179 | [ROR]: samples/resources/ruby_on_rails.png "Logo Title Text 2" 180 | 181 | ## LaTeX 182 | 183 | MDAnki and Anki can support LaTeX. Install LaTeX for your OS and use the `[latex]` attribute within Markdown files. 184 | 185 | ``` 186 | [latex]\\[e^x -1 = 3\\][/latex] 187 | ``` 188 | 189 | ## Memory limit 190 | 191 | Converting a big Markdown file you can get a memory limit error like this: 192 | 193 | > Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value 16777216... 194 | 195 | For overcoming this error, replace `sql.js`: 196 | 197 | ```bash 198 | cp node_modules/sql.js/js/sql-memory-growth.js node_modules/sql.js/js/sql.js 199 | ``` 200 | 201 | More info [here](https://github.com/sql-js/sql.js#versions-of-sqljs-included-in-the-distributed-artifacts). 202 | 203 | ## License 204 | MIT License, Copyright (c) 2020, Oleksandr Shlinchak. 205 | 206 | ## Changelog 207 | [Changelog](./CHANGELOG.md) 208 | 209 | -------------------------------------------------------------------------------- /__mocks__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "rules": { 6 | "no-underscore-dangle": "off", 7 | "global-require": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /__mocks__/anki-apkg-export.js: -------------------------------------------------------------------------------- 1 | const ankiExport = jest.genMockFromModule('anki-apkg-export'); 2 | 3 | let saveReturnValue = null; 4 | 5 | function __setSaveReturnValue(value) { 6 | saveReturnValue = value; 7 | } 8 | ankiExport.default.mockImplementation(() => ({ 9 | addCard : jest.fn(), 10 | addMedia: jest.fn(), 11 | save : jest.fn(() => saveReturnValue), 12 | })); 13 | 14 | ankiExport.__setSaveReturnValue = __setSaveReturnValue; 15 | 16 | module.exports = ankiExport; 17 | -------------------------------------------------------------------------------- /__tests__/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "rules": { 6 | "no-underscore-dangle": "off", 7 | "global-require": "off", 8 | "import/order": "off" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /__tests__/configs/index.test.js: -------------------------------------------------------------------------------- 1 | jest.mock('../../src/configs/settings', () => ({ 2 | setting: 'value', 3 | })); 4 | 5 | 6 | describe('configs', () => { 7 | let configs; 8 | 9 | afterEach(() => { 10 | jest.restoreAllMocks(); 11 | jest.resetModules(); 12 | }); 13 | 14 | describe('without provided config file', () => { 15 | beforeEach(() => { 16 | jest.mock('yargs', () => ({ 17 | argv: {}, 18 | })); 19 | }); 20 | 21 | test('returns default settings', () => { 22 | configs = require('../../src/configs'); 23 | 24 | expect(configs).toEqual({ setting: 'value' }); 25 | }); 26 | }); 27 | 28 | describe('with provided config file', () => { 29 | beforeEach(() => { 30 | jest.mock('fs', () => ({ 31 | readFileSync: () => '{"setting":"another value"}', 32 | })); 33 | jest.mock('yargs', () => ({ 34 | argv: { config: './config.json' }, 35 | })); 36 | }); 37 | 38 | test('returns overridden settings', () => { 39 | configs = require('../../src/configs'); 40 | 41 | expect(configs).toEqual({ setting: 'another value' }); 42 | }); 43 | 44 | test('handles an error', () => { 45 | jest.spyOn(JSON, 'parse').mockImplementation(() => { 46 | throw new Error('cannot parse'); 47 | }); 48 | jest.spyOn(console, 'log').mockImplementationOnce(() => {}); 49 | 50 | configs = require('../../src/configs'); 51 | 52 | expect(console.log).toHaveBeenCalledTimes(1); 53 | expect(console.log.mock.calls[0][0].message).toEqual('cannot parse'); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /__tests__/file_serializer.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const FileSerializer = require('../src/file_serializer'); 3 | 4 | jest.mock('fs'); 5 | 6 | 7 | describe('FileSerializer', () => { 8 | let serializer; 9 | const filePath = 'file.md'; 10 | const mediaPath = 'image.png'; 11 | let markdown; 12 | 13 | afterEach(() => { 14 | jest.restoreAllMocks(); 15 | }); 16 | 17 | describe('#transform', () => { 18 | const readFileSyncMock = (p) => { 19 | if (p === filePath) { return markdown; } 20 | if (p.includes(mediaPath)) { return 'data'; } 21 | return null; 22 | }; 23 | 24 | beforeEach(() => { 25 | markdown = `# Deck name\n## Title\nbody\n![media](${mediaPath})`; 26 | jest.spyOn(fs, 'readFileSync').mockImplementation(readFileSyncMock); 27 | }); 28 | 29 | test('serializes a markdown to the deck data', async () => { 30 | serializer = new FileSerializer(filePath); 31 | 32 | const { 33 | deckName, 34 | cards, 35 | media, 36 | } = await serializer.transform(); 37 | 38 | expect(deckName).toEqual('Deck name'); 39 | expect(cards.length).toEqual(1); 40 | expect(cards[0].front.replace(/\n/g, '')).toEqual('

Title

'); 41 | expect(cards[0].back.replace(/\n/g, '')).toEqual('

body
media

'); 42 | expect(media.length).toEqual(1); 43 | expect(media[0].data).toEqual('data'); 44 | expect(media[0].fileName).toEqual('8d777f385d3dfec8815d20f7496026dc.png'); 45 | }); 46 | 47 | test('returns without a deck name if it\'s not specified in the markdown', async () => { 48 | markdown = `## Title\nbody\n![media](${mediaPath})`; 49 | serializer = new FileSerializer(filePath); 50 | 51 | const { deckName } = await serializer.transform(); 52 | 53 | expect(deckName).toEqual(null); 54 | }); 55 | 56 | test('keeps media unique', async () => { 57 | markdown = `## Title\nbody\n![media](${mediaPath})![media](${mediaPath})`; 58 | serializer = new FileSerializer(filePath); 59 | 60 | const { media } = await serializer.transform(); 61 | 62 | expect(media.length).toEqual(1); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /__tests__/models/card.test.js: -------------------------------------------------------------------------------- 1 | const Card = require('../../src/models/card'); 2 | 3 | describe('Card', () => { 4 | let card; 5 | 6 | beforeEach(() => { 7 | card = new Card('front', 'back'); 8 | }); 9 | 10 | describe('#addTag', () => { 11 | test('adds a tag to tags', () => { 12 | card.addTag('tag'); 13 | 14 | expect(card.tags).toEqual(['tag']); 15 | }); 16 | 17 | test('sanitizes tag names', () => { 18 | card.addTag(' tag1 '); 19 | card.addTag('tag 2'); 20 | 21 | expect(card.tags[0]).toEqual('tag1'); 22 | expect(card.tags[1]).toEqual('tag_2'); 23 | }); 24 | 25 | test('does not add empty string', () => { 26 | card.addTag(' '); 27 | 28 | expect(card.tags.length).toEqual(0); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /__tests__/models/deck.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const ankiExport = require('anki-apkg-export'); 3 | 4 | const Deck = require('../../src/models/deck'); 5 | const Card = require('../../src/models/card'); 6 | const Media = require('../../src/models/media'); 7 | 8 | jest.mock('fs'); 9 | jest.mock('anki-apkg-export'); 10 | 11 | 12 | describe('Deck', () => { 13 | let deck; 14 | 15 | beforeEach(() => { 16 | deck = new Deck('deck name'); 17 | }); 18 | 19 | afterEach(() => { 20 | jest.restoreAllMocks(); 21 | }); 22 | 23 | describe('#addCard', () => { 24 | test('adds a card to the collection', () => { 25 | deck.addCard('card'); 26 | 27 | expect(deck.cards.length).toEqual(1); 28 | }); 29 | }); 30 | 31 | describe('#addMedia', () => { 32 | test('adds a media item to the collection', () => { 33 | deck.addMedia('media'); 34 | 35 | expect(deck.mediaCollection.length).toEqual(1); 36 | }); 37 | }); 38 | 39 | describe('#save', () => { 40 | beforeEach(() => { 41 | ankiExport.__setSaveReturnValue('zip'); 42 | jest.spyOn(console, 'log').mockImplementation(() => {}); 43 | deck = new Deck('deck name'); 44 | deck.addCard(new Card('front', 'back', ['tag', 'another_tag'])); 45 | deck.addMedia(new Media('data', 'image.png')); 46 | }); 47 | 48 | test('exports a card and a media item to the apkg file', async () => { 49 | await deck.save('anki.apkg'); 50 | 51 | expect(fs.writeFileSync).toHaveBeenCalledWith('anki.apkg', 'zip', 'binary'); 52 | expect(console.log).toHaveBeenCalledWith('The deck "deck name" has been generated in anki.apkg'); 53 | }); 54 | 55 | test('catches an error', async () => { 56 | jest.spyOn(fs, 'writeFileSync').mockImplementationOnce(() => { throw new Error('Cannot write file'); }); 57 | 58 | await deck.save('anki.apkg'); 59 | 60 | expect(console.log).toHaveBeenCalledTimes(1); 61 | expect(console.log.mock.calls[0][0].message).toEqual('Cannot write file'); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /__tests__/models/media.test.js: -------------------------------------------------------------------------------- 1 | const Media = require('../../src/models/media'); 2 | 3 | describe('Media', () => { 4 | let media; 5 | 6 | beforeEach(() => { 7 | media = new Media('data', 'image.png'); 8 | }); 9 | 10 | describe('#checksum', () => { 11 | test('generates a digest', () => { 12 | expect(media.checksum).toEqual('8d777f385d3dfec8815d20f7496026dc'); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /__tests__/parsers/card_parser.test.js: -------------------------------------------------------------------------------- 1 | const CardParser = require('../../src/parsers/card_parser'); 2 | 3 | 4 | describe('CardParser', () => { 5 | let parser; 6 | const markdown = '## Title\nbody\n[#tag]()'; 7 | const markdownWithMultipleLines = '## Title\nfront\n%\nback\n[#tag]()'; 8 | 9 | beforeEach(() => { 10 | parser = new CardParser(); 11 | }); 12 | 13 | afterEach(() => { 14 | jest.restoreAllMocks(); 15 | }); 16 | 17 | describe('#parse', () => { 18 | test('returns null for a blank string', async () => { 19 | const data = await parser.parse(' '); 20 | 21 | expect(data).toEqual(null); 22 | }); 23 | 24 | test('returns null when undefined is passed', async () => { 25 | const data = await parser.parse(undefined); 26 | 27 | expect(data).toEqual(null); 28 | }); 29 | 30 | test('creates a card with HTML sides', async () => { 31 | const card = await parser.parse(markdown); 32 | 33 | expect(card.front).toEqual('

Title

\n'); 34 | expect(card.back).toEqual('

body

\n'); 35 | expect(card.tags.length).toEqual(1); 36 | expect(card.tags[0]).toEqual('tag'); 37 | }); 38 | 39 | test('creates a card with raw sides', async () => { 40 | parser.options.convertToHtml = false; 41 | const card = await parser.parse(markdown); 42 | 43 | expect(card.front).toEqual('## Title'); 44 | expect(card.back).toEqual('body'); 45 | expect(card.tags.length).toEqual(1); 46 | expect(card.tags[0]).toEqual('tag'); 47 | }); 48 | 49 | test('creates a multi-line card with HTML sides', async () => { 50 | const card = await parser.parse(markdownWithMultipleLines); 51 | 52 | expect(card.front.replace(/\n/g, '')).toEqual('

Title

front

'); 53 | expect(card.back).toEqual('

back

\n'); 54 | expect(card.tags.length).toEqual(1); 55 | expect(card.tags[0]).toEqual('tag'); 56 | }); 57 | 58 | test('skips first blank lines for back', async () => { 59 | const card = await parser.parse('## Title\n \nbody'); 60 | 61 | expect(card.front.replace(/\n/g, '')).toEqual('

Title

'); 62 | expect(card.back.replace(/\n/g, '')).toEqual('

body

'); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /__tests__/parsers/md_parser.test.js: -------------------------------------------------------------------------------- 1 | const MdParser = require('../../src/parsers/md_parser'); 2 | 3 | jest.mock('marked'); 4 | jest.mock('prismjs'); 5 | const marked = require('marked'); 6 | const Prism = require('prismjs'); 7 | 8 | describe('MdParser', () => { 9 | let parser; 10 | 11 | beforeEach(() => { 12 | parser = new MdParser(); 13 | }); 14 | 15 | afterEach(() => { 16 | jest.restoreAllMocks(); 17 | }); 18 | 19 | describe('#parse', () => { 20 | test('calls the marked parse method with the specified string', async () => { 21 | jest.spyOn(marked, 'parse').mockImplementation((str, cb) => { 22 | cb(null, str); 23 | }); 24 | 25 | const data = await parser.parse('string'); 26 | 27 | expect(data).toEqual('string'); 28 | }); 29 | 30 | test('returns an error', async () => { 31 | jest.spyOn(marked, 'parse').mockImplementation((str, cb) => { 32 | cb(new Error('cannot parse')); 33 | }); 34 | 35 | await parser.parse('string') 36 | .catch((err) => { 37 | expect(err.message).toEqual('cannot parse'); 38 | }); 39 | }); 40 | }); 41 | 42 | describe('#highlight', () => { 43 | const originLanguages = Prism.languages; 44 | 45 | beforeEach(() => { 46 | Prism.languages = { 47 | js : 'js lang', 48 | bash: 'bash lang', 49 | }; 50 | jest.spyOn(Prism, 'highlight').mockReturnValue('highlighted code'); 51 | }); 52 | 53 | afterEach(() => { 54 | Prism.languages = originLanguages; 55 | }); 56 | 57 | test('calls the prism highlight method', () => { 58 | const data = parser.highlight('code', 'js'); 59 | 60 | expect(data).toEqual('highlighted code'); 61 | }); 62 | 63 | test('calls the prism highlight method with a default language', () => { 64 | const data = parser.highlight('code', null); 65 | 66 | expect(data).toEqual('highlighted code'); 67 | expect(Prism.highlight).toHaveBeenCalledWith('code', 'bash lang', 'bash'); 68 | }); 69 | 70 | test('returns a raw code for not aware language in prism', () => { 71 | const data = parser.highlight('code', 'fake'); 72 | 73 | expect(data).toEqual('code'); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /__tests__/parsers/media_parser.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const axios = require('axios'); 3 | 4 | const MediaParser = require('../../src/parsers/media_parser'); 5 | 6 | jest.mock('fs'); 7 | jest.mock('axios'); 8 | 9 | describe('MediaParser', () => { 10 | let parser; 11 | 12 | beforeEach(() => { 13 | parser = new MediaParser('source.md'); 14 | }); 15 | 16 | afterEach(() => { 17 | jest.restoreAllMocks(); 18 | }); 19 | 20 | describe('#parse', () => { 21 | test('returns a blank string', async () => { 22 | const data = await parser.parse(''); 23 | 24 | expect(data).toEqual(''); 25 | }); 26 | 27 | test('returns the same card data', async () => { 28 | const data = await parser.parse('

Title

'); 29 | 30 | expect(data).toEqual('

Title

'); 31 | }); 32 | 33 | test('parses locale media file', async () => { 34 | jest.spyOn(fs, 'readFileSync').mockImplementationOnce(() => 'data'); 35 | 36 | const data = await parser.parse(''); 37 | 38 | expect(data).toEqual(''); 39 | expect(parser.mediaList.length).toEqual(1); 40 | expect(parser.mediaList[0].data).toEqual('data'); 41 | expect(parser.mediaList[0].fileName).toEqual('8d777f385d3dfec8815d20f7496026dc.png'); 42 | }); 43 | 44 | test('parses remote media file', async () => { 45 | jest.spyOn(axios, 'get').mockImplementationOnce(() => ({ 46 | data: 'data', 47 | })); 48 | 49 | const data = await parser.parse(''); 50 | 51 | expect(data).toEqual(''); 52 | expect(parser.mediaList.length).toEqual(1); 53 | expect(parser.mediaList[0].data).toEqual('data'); 54 | expect(parser.mediaList[0].fileName).toEqual('8d777f385d3dfec8815d20f7496026dc.png'); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /__tests__/transformer.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const glob = require('glob'); 3 | 4 | let Transformer = require('../src/transformer'); 5 | const Deck = require('../src/models/deck'); 6 | 7 | jest.mock('fs'); 8 | jest.mock('glob'); 9 | 10 | 11 | describe('Transformer', () => { 12 | let transformer; 13 | const sourceFilePath = 'file.md'; 14 | 15 | describe('#transform', () => { 16 | const markdown = '# Deck title\n## Title\nbody\n![#image](image.png)'; 17 | const sourceDirectoryPath = '/path/to/directory'; 18 | 19 | beforeEach(() => { 20 | jest.spyOn(console, 'log').mockImplementationOnce(() => {}); 21 | jest.spyOn(process, 'exit').mockImplementationOnce(() => {}); 22 | }); 23 | 24 | describe('validations', () => { 25 | test('validates presence of source file', async () => { 26 | transformer = new Transformer('fake/path', 'path/to/anki.apkg'); 27 | jest.spyOn(transformer, 'transformToDeck').mockResolvedValue(); 28 | jest.spyOn(transformer, 'validateExt').mockImplementation(); 29 | 30 | await transformer.transform(); 31 | 32 | expect(console.log).toHaveBeenLastCalledWith('fake/path does not exists'); 33 | expect(process.exit).toHaveBeenLastCalledWith(1); 34 | }); 35 | 36 | test('validates file extension', async () => { 37 | transformer = new Transformer('path/to/file.txt', 'path/to/anki.apkg'); 38 | jest.spyOn(transformer, 'transformToDeck').mockResolvedValue(); 39 | jest.spyOn(transformer, 'validatePath').mockImplementation(); 40 | 41 | await transformer.transform(); 42 | 43 | expect(console.log).toHaveBeenLastCalledWith('path/to/file.txt has not allowed extension'); 44 | expect(process.exit).toHaveBeenLastCalledWith(1); 45 | }); 46 | }); 47 | 48 | describe('parse a directory', () => { 49 | beforeEach(() => { 50 | jest.spyOn(Transformer.prototype, 'addResourcesToDeck').mockImplementation(); 51 | jest.spyOn(Transformer.prototype, 'calculateDeckName').mockReturnValue('deck name'); 52 | jest.spyOn(Deck.prototype, 'save').mockResolvedValue({ 53 | save: jest.fn(), 54 | }); 55 | fs.lstatSync.mockReturnValue({ 56 | isDirectory: () => true, 57 | }); 58 | fs.readFileSync.mockReturnValue(markdown); 59 | fs.existsSync.mockReturnValue(true); 60 | glob.sync.mockReturnValue([sourceFilePath]); 61 | }); 62 | 63 | beforeEach(async () => { 64 | transformer = new Transformer(sourceDirectoryPath, 'path/to/anki.apkg'); 65 | await transformer.transform(); 66 | }); 67 | 68 | test('creates a deck', () => { 69 | expect(transformer.deck.save).toHaveBeenCalledWith('path/to/anki.apkg'); 70 | }); 71 | 72 | test('sets the default deck name', () => { 73 | expect(transformer.deck.name).toEqual('deck name'); 74 | }); 75 | }); 76 | 77 | describe('parse a file', () => { 78 | beforeEach(() => { 79 | jest.spyOn(Transformer.prototype, 'addResourcesToDeck').mockImplementation(); 80 | jest.spyOn(Deck.prototype, 'save').mockResolvedValue({ 81 | save: jest.fn(), 82 | }); 83 | fs.lstatSync.mockReturnValue({ 84 | isDirectory: () => false, 85 | }); 86 | fs.readFileSync.mockReturnValue(markdown); 87 | fs.existsSync.mockReturnValue(true); 88 | }); 89 | 90 | beforeEach(async () => { 91 | transformer = new Transformer(sourceFilePath, 'path/to/anki.apkg'); 92 | await transformer.transform(); 93 | }); 94 | 95 | test('creates a deck', () => { 96 | expect(transformer.deck.save).toHaveBeenCalledWith('path/to/anki.apkg'); 97 | }); 98 | 99 | test('sets a deck name based on the title from the markdown', () => { 100 | expect(transformer.deck.name).toEqual('deck name'); 101 | }); 102 | }); 103 | 104 | describe('no cards found', () => { 105 | beforeEach(() => { 106 | jest.spyOn(Transformer.prototype, 'validate').mockImplementation(); 107 | jest.spyOn(Transformer.prototype, 'addResourcesToDeck').mockImplementation(); 108 | jest.spyOn(Deck.prototype, 'save').mockResolvedValue({ 109 | save: jest.fn(), 110 | }); 111 | fs.lstatSync.mockReturnValue({ 112 | isDirectory: () => false, 113 | }); 114 | fs.readFileSync.mockReturnValue('markdown without cards'); 115 | }); 116 | 117 | beforeEach(async () => { 118 | transformer = new Transformer(sourceFilePath, 'path/to/anki.apkg'); 119 | await transformer.transform(); 120 | }); 121 | 122 | test('exits process', () => { 123 | expect(console.log).toHaveBeenLastCalledWith('No cards found. Check you markdown file(s)'); 124 | expect(process.exit).toHaveBeenLastCalledWith(1); 125 | }); 126 | }); 127 | }); 128 | 129 | describe('#calculateDeckName', () => { 130 | beforeEach(() => { 131 | jest.resetModules(); 132 | }); 133 | 134 | test('generates deck name from passed arguments', () => { 135 | jest.mock('yargs', () => ({ 136 | argv: { deck: 'deck name' }, 137 | })); 138 | Transformer = require('../src/transformer'); 139 | transformer = new Transformer(sourceFilePath, 'anki.apkg'); 140 | 141 | expect( 142 | transformer.calculateDeckName(), 143 | ).toEqual('deck name'); 144 | }); 145 | 146 | test('generates deck name from markdown title', () => { 147 | jest.mock('yargs', () => ({ 148 | argv: { deck: null }, 149 | })); 150 | Transformer = require('../src/transformer'); 151 | transformer = new Transformer(sourceFilePath, 'anki.apkg'); 152 | 153 | expect( 154 | transformer.calculateDeckName('calculated deck name'), 155 | ).toEqual('calculated deck name'); 156 | }); 157 | 158 | test('generates deck name from default configs', () => { 159 | jest.mock('yargs', () => ({ 160 | argv: { deck: null }, 161 | })); 162 | Transformer = require('../src/transformer'); 163 | transformer = new Transformer(sourceFilePath, 'anki.apkg'); 164 | 165 | expect( 166 | transformer.calculateDeckName(), 167 | ).toEqual('mdanki'); 168 | }); 169 | }); 170 | 171 | describe('required resources', () => { 172 | let deck; 173 | 174 | beforeEach(() => { 175 | deck = new Deck(); 176 | jest.spyOn(deck, 'addMedia'); 177 | }); 178 | 179 | test('adds with dark template', () => { 180 | Transformer = require('../src/transformer'); 181 | transformer = new Transformer(sourceFilePath, 'anki.apkg'); 182 | transformer.deck = deck; 183 | 184 | transformer.addResourcesToDeck(); 185 | 186 | expect(deck.addMedia).toHaveBeenCalledTimes(3); 187 | expect(deck.addMedia.mock.calls[2][0].fileName).toEqual('_highlight_dark.css'); 188 | }); 189 | 190 | test('adds with default template', () => { 191 | jest.resetModules(); 192 | 193 | jest.mock('fs', () => ({ 194 | readFileSync: () => '{"code":{"template":"default"}}', 195 | })); 196 | jest.mock('yargs', () => ({ 197 | argv: { config: './config.json' }, 198 | })); 199 | 200 | Transformer = require('../src/transformer'); 201 | transformer = new Transformer(sourceFilePath, 'anki.apkg'); 202 | transformer.deck = deck; 203 | 204 | transformer.addResourcesToDeck(); 205 | 206 | expect(deck.addMedia).toHaveBeenCalledTimes(3); 207 | expect(deck.addMedia.mock.calls[2][0].fileName).toEqual('_highlight_default.css'); 208 | }); 209 | }); 210 | }); 211 | -------------------------------------------------------------------------------- /__tests__/utils.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | sanitizeString, 3 | trimArrayStart, 4 | trimArrayEnd, 5 | trimArray, 6 | } = require('../src/utils'); 7 | 8 | describe('#sanitizeString', () => { 9 | test('trims a string', () => { 10 | expect(sanitizeString(' tag ')).toEqual('tag'); 11 | }); 12 | 13 | test('replaces spaces with underscore', () => { 14 | expect(sanitizeString('tag 1')).toEqual('tag_1'); 15 | }); 16 | }); 17 | 18 | describe('#trimArray', () => { 19 | test('removes empty values from both sides of the array', () => { 20 | const array = [null, 1, '']; 21 | expect(trimArray(array)).toEqual([1]); 22 | }); 23 | }); 24 | 25 | describe('#trimArrayStart', () => { 26 | test('removes empty values in the array from the begging', () => { 27 | const array = [null, 1, '']; 28 | expect(trimArrayStart(array)).toEqual([1, '']); 29 | }); 30 | }); 31 | 32 | describe('#trimArrayEnd', () => { 33 | test('removes empty values in the array from the end', () => { 34 | const array = [null, 1, '']; 35 | expect(trimArrayEnd(array)).toEqual([null, 1]); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const testPathIgnorePatterns = [ 2 | '/node_modules/', 3 | ]; 4 | 5 | module.exports = { 6 | testPathIgnorePatterns, 7 | coverageDirectory : './coverage/', 8 | testEnvironment : 'node', 9 | coverageThreshold : { 10 | global: { 11 | branches : 100, 12 | functions : 100, 13 | lines : 100, 14 | statements: 100, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdanki", 3 | "version": "1.1.2", 4 | "description": "Convert markdown files to anki cards", 5 | "keywords": [ 6 | "anki", 7 | "markdown" 8 | ], 9 | "main": "src/index.js", 10 | "bin": { 11 | "mdanki": "src/index.js" 12 | }, 13 | "scripts": { 14 | "ci": "npm run coverage", 15 | "coverage": "jest --coverage", 16 | "test": "jest", 17 | "watch-test": "jest --coverage --watchAll" 18 | }, 19 | "author": "Oleksandr Shlinchak ", 20 | "homepage": "https://github.com/ashlinchak/mdanki", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/ashlinchak/mdanki.git" 24 | }, 25 | "license": "MIT", 26 | "dependencies": { 27 | "anki-apkg-export": "^4.0.3", 28 | "axios": "^0.23.0", 29 | "bluebird": "^3.7.2", 30 | "glob": "^7.2.0", 31 | "lodash": "^4.17.21", 32 | "marked": "^0.8.0", 33 | "prismjs": "^1.25.0", 34 | "yargs": "^17.2.1" 35 | }, 36 | "devDependencies": { 37 | "@types/jest": "^27.0.2", 38 | "eslint": "^8.0.0", 39 | "eslint-config-airbnb-base": "^14.2.1", 40 | "eslint-plugin-import": "^2.25.2", 41 | "jest": "^27.2.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /resources/dark.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.19.0 2 | https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+actionscript+applescript+aspnet+bash+basic+batch+c+csharp+cpp+coffeescript+d+dart+erlang+fsharp+go+graphql+groovy+handlebars+java+json+latex+less+livescript+lua+makefile+markdown+markup-templating+nginx+objectivec+pascal+perl+php+powershell+python+r+ruby+rust+sass+scheme+smalltalk+smarty+sql+stylus+swift+typescript+vim+yaml */ 3 | /** 4 | * okaidia theme for JavaScript, CSS and HTML 5 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 6 | * @author ocodia 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: #f8f8f2; 12 | background: none; 13 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 15 | font-size: 1em; 16 | text-align: left; 17 | white-space: pre; 18 | word-spacing: normal; 19 | word-break: normal; 20 | word-wrap: normal; 21 | line-height: 1.5; 22 | 23 | -moz-tab-size: 4; 24 | -o-tab-size: 4; 25 | tab-size: 4; 26 | 27 | -webkit-hyphens: none; 28 | -moz-hyphens: none; 29 | -ms-hyphens: none; 30 | hyphens: none; 31 | } 32 | 33 | /* Code blocks */ 34 | pre[class*="language-"] { 35 | padding: 1em; 36 | margin: .5em 0; 37 | overflow: auto; 38 | border-radius: 0.3em; 39 | } 40 | 41 | :not(pre) > code[class*="language-"], 42 | pre[class*="language-"] { 43 | background: #272822; 44 | } 45 | 46 | /* Inline code */ 47 | :not(pre) > code[class*="language-"] { 48 | padding: .1em; 49 | border-radius: .3em; 50 | white-space: normal; 51 | } 52 | 53 | .token.comment, 54 | .token.prolog, 55 | .token.doctype, 56 | .token.cdata { 57 | color: slategray; 58 | } 59 | 60 | .token.punctuation { 61 | color: #f8f8f2; 62 | } 63 | 64 | .token.namespace { 65 | opacity: .7; 66 | } 67 | 68 | .token.property, 69 | .token.tag, 70 | .token.constant, 71 | .token.symbol, 72 | .token.deleted { 73 | color: #f92672; 74 | } 75 | 76 | .token.boolean, 77 | .token.number { 78 | color: #ae81ff; 79 | } 80 | 81 | .token.selector, 82 | .token.attr-name, 83 | .token.string, 84 | .token.char, 85 | .token.builtin, 86 | .token.inserted { 87 | color: #a6e22e; 88 | } 89 | 90 | .token.operator, 91 | .token.entity, 92 | .token.url, 93 | .language-css .token.string, 94 | .style .token.string, 95 | .token.variable { 96 | color: #f8f8f2; 97 | } 98 | 99 | .token.atrule, 100 | .token.attr-value, 101 | .token.function, 102 | .token.class-name { 103 | color: #e6db74; 104 | } 105 | 106 | .token.keyword { 107 | color: #66d9ef; 108 | } 109 | 110 | .token.regex, 111 | .token.important { 112 | color: #fd971f; 113 | } 114 | 115 | .token.important, 116 | .token.bold { 117 | font-weight: bold; 118 | } 119 | .token.italic { 120 | font-style: italic; 121 | } 122 | 123 | .token.entity { 124 | cursor: help; 125 | } 126 | 127 | -------------------------------------------------------------------------------- /resources/default.css: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.19.0 2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+actionscript+applescript+aspnet+bash+basic+batch+c+csharp+cpp+coffeescript+d+dart+erlang+fsharp+go+graphql+groovy+handlebars+java+json+latex+less+livescript+lua+makefile+markdown+markup-templating+nginx+objectivec+pascal+perl+php+powershell+python+r+ruby+rust+sass+scheme+smalltalk+smarty+sql+stylus+swift+typescript+vim+yaml */ 3 | /** 4 | * prism.js default theme for JavaScript, CSS and HTML 5 | * Based on dabblet (http://dabblet.com) 6 | * @author Lea Verou 7 | */ 8 | 9 | code[class*="language-"], 10 | pre[class*="language-"] { 11 | color: black; 12 | background: none; 13 | text-shadow: 0 1px white; 14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 15 | font-size: 1em; 16 | text-align: left; 17 | white-space: pre; 18 | word-spacing: normal; 19 | word-break: normal; 20 | word-wrap: normal; 21 | line-height: 1.5; 22 | 23 | -moz-tab-size: 4; 24 | -o-tab-size: 4; 25 | tab-size: 4; 26 | 27 | -webkit-hyphens: none; 28 | -moz-hyphens: none; 29 | -ms-hyphens: none; 30 | hyphens: none; 31 | } 32 | 33 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 34 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 35 | text-shadow: none; 36 | background: #b3d4fc; 37 | } 38 | 39 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 40 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 41 | text-shadow: none; 42 | background: #b3d4fc; 43 | } 44 | 45 | @media print { 46 | code[class*="language-"], 47 | pre[class*="language-"] { 48 | text-shadow: none; 49 | } 50 | } 51 | 52 | /* Code blocks */ 53 | pre[class*="language-"] { 54 | padding: 1em; 55 | margin: .5em 0; 56 | overflow: auto; 57 | } 58 | 59 | :not(pre) > code[class*="language-"], 60 | pre[class*="language-"] { 61 | background: #f5f2f0; 62 | } 63 | 64 | /* Inline code */ 65 | :not(pre) > code[class*="language-"] { 66 | padding: .1em; 67 | border-radius: .3em; 68 | white-space: normal; 69 | } 70 | 71 | .token.comment, 72 | .token.prolog, 73 | .token.doctype, 74 | .token.cdata { 75 | color: slategray; 76 | } 77 | 78 | .token.punctuation { 79 | color: #999; 80 | } 81 | 82 | .token.namespace { 83 | opacity: .7; 84 | } 85 | 86 | .token.property, 87 | .token.tag, 88 | .token.boolean, 89 | .token.number, 90 | .token.constant, 91 | .token.symbol, 92 | .token.deleted { 93 | color: #905; 94 | } 95 | 96 | .token.selector, 97 | .token.attr-name, 98 | .token.string, 99 | .token.char, 100 | .token.builtin, 101 | .token.inserted { 102 | color: #690; 103 | } 104 | 105 | .token.operator, 106 | .token.entity, 107 | .token.url, 108 | .language-css .token.string, 109 | .style .token.string { 110 | color: #9a6e3a; 111 | background: hsla(0, 0%, 100%, .5); 112 | } 113 | 114 | .token.atrule, 115 | .token.attr-value, 116 | .token.keyword { 117 | color: #07a; 118 | } 119 | 120 | .token.function, 121 | .token.class-name { 122 | color: #DD4A68; 123 | } 124 | 125 | .token.regex, 126 | .token.important, 127 | .token.variable { 128 | color: #e90; 129 | } 130 | 131 | .token.important, 132 | .token.bold { 133 | font-weight: bold; 134 | } 135 | .token.italic { 136 | font-style: italic; 137 | } 138 | 139 | .token.entity { 140 | cursor: help; 141 | } 142 | 143 | -------------------------------------------------------------------------------- /resources/highlight.js: -------------------------------------------------------------------------------- 1 | document.querySelectorAll('pre code').forEach((block) => { 2 | // eslint-disable-next-line no-undef 3 | hljs.highlightBlock(block); 4 | }); 5 | -------------------------------------------------------------------------------- /resources/prism.js: -------------------------------------------------------------------------------- 1 | /* PrismJS 1.19.0 2 | https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+actionscript+applescript+aspnet+bash+basic+batch+c+csharp+cpp+coffeescript+d+dart+erlang+fsharp+go+graphql+groovy+handlebars+java+json+latex+less+livescript+lua+makefile+markdown+markup-templating+nginx+objectivec+pascal+perl+php+powershell+python+r+ruby+rust+sass+scheme+smalltalk+smarty+sql+stylus+swift+typescript+vim+yaml */ 3 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,C={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof _?new _(e.type,C.util.encode(e.content),e.alias):Array.isArray(e)?e.map(C.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(k instanceof _)){if(h&&y!=n.length-1){if(c.lastIndex=v,!(O=c.exec(e)))break;for(var b=O.index+(f&&O[1]?O[1].length:0),w=O.index+O[0].length,A=y,P=v,x=n.length;A"+r.content+""},!u.document)return u.addEventListener&&(C.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,t=n.code,a=n.immediateClose;u.postMessage(C.highlight(t,C.languages[r],r)),a&&u.close()},!1)),C;var e=C.util.currentScript();function r(){C.manual||C.highlightAll()}if(e&&(C.filename=e.src,e.hasAttribute("data-manual")&&(C.manual=!0)),!C.manual){var t=document.readyState;"loading"===t||"interactive"===t&&e&&e.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)}return C}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); 4 | Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:(?!)*\]\s*)?>/i,greedy:!0},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var n={"included-cdata":{pattern://i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[\\s\\S]*?>)(?:\\s*|[\\s\\S])*?(?=<\\/__>)".replace(/__/g,a),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; 5 | !function(s){var e=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\((?!\s*\))\s*)(?:[^()]|\((?:[^()]|\([^()]*\))*\))+?(?=\s*\))/,lookbehind:!0,alias:"selector"}}},url:{pattern:RegExp("url\\((?:"+e.source+"|[^\n\r()]*)\\)","i"),inside:{function:/^url/i,punctuation:/^\(|\)$/}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+e.source+")*?(?=\\s*\\{)"),string:{pattern:e,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),s.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:t.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:s.languages.css}},alias:"language-css"}},t.tag))}(Prism); 6 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; 7 | Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&|\|\||[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?[.?]?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*[\s\S]*?\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript; 8 | Prism.languages.actionscript=Prism.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|else|extends|finally|for|function|if|implements|import|in|instanceof|interface|internal|is|native|new|null|package|private|protected|public|return|super|switch|this|throw|try|typeof|use|var|void|while|with|dynamic|each|final|get|include|namespace|native|override|set|static)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<>?>?|[!=]=?)=?|[~?@]/}),Prism.languages.actionscript["class-name"].alias="function",Prism.languages.markup&&Prism.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:Prism.languages.markup}}); 9 | Prism.languages.applescript={comment:[/\(\*(?:\(\*[\s\S]*?\*\)|[\s\S])*?\*\)/,/--.+/,/#.+/],string:/"(?:\\.|[^"\\\r\n])*"/,number:/(?:\b\d+\.?\d*|\B\.\d+)(?:e-?\d+)?\b/i,operator:[/[&=≠≤≥*+\-\/÷^]|[<>]=?/,/\b(?:(?:start|begin|end)s? with|(?:(?:does not|doesn't) contain|contains?)|(?:is|isn't|is not) (?:in|contained by)|(?:(?:is|isn't|is not) )?(?:greater|less) than(?: or equal)?(?: to)?|(?:(?:does not|doesn't) come|comes) (?:before|after)|(?:is|isn't|is not) equal(?: to)?|(?:(?:does not|doesn't) equal|equals|equal to|isn't|is not)|(?:a )?(?:ref(?: to)?|reference to)|(?:and|or|div|mod|as|not))\b/],keyword:/\b(?:about|above|after|against|apart from|around|aside from|at|back|before|beginning|behind|below|beneath|beside|between|but|by|considering|continue|copy|does|eighth|else|end|equal|error|every|exit|false|fifth|first|for|fourth|from|front|get|given|global|if|ignoring|in|instead of|into|is|it|its|last|local|me|middle|my|ninth|of|on|onto|out of|over|prop|property|put|repeat|return|returning|second|set|seventh|since|sixth|some|tell|tenth|that|the|then|third|through|thru|timeout|times|to|transaction|true|try|until|where|while|whose|with|without)\b/,class:{pattern:/\b(?:alias|application|boolean|class|constant|date|file|integer|list|number|POSIX file|real|record|reference|RGB color|script|text|centimetres|centimeters|feet|inches|kilometres|kilometers|metres|meters|miles|yards|square feet|square kilometres|square kilometers|square metres|square meters|square miles|square yards|cubic centimetres|cubic centimeters|cubic feet|cubic inches|cubic metres|cubic meters|cubic yards|gallons|litres|liters|quarts|grams|kilograms|ounces|pounds|degrees Celsius|degrees Fahrenheit|degrees Kelvin)\b/,alias:"builtin"},punctuation:/[{}():,¬«»《》]/}; 10 | Prism.languages.csharp=Prism.languages.extend("clike",{keyword:/\b(?:abstract|add|alias|as|ascending|async|await|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|descending|do|double|dynamic|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|from|get|global|goto|group|if|implicit|in|int|interface|internal|into|is|join|let|lock|long|namespace|new|null|object|operator|orderby|out|override|params|partial|private|protected|public|readonly|ref|remove|return|sbyte|sealed|select|set|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|value|var|virtual|void|volatile|where|while|yield)\b/,string:[{pattern:/@("|')(?:\1\1|\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*?\1/,greedy:!0}],"class-name":[{pattern:/\b[A-Z]\w*(?:\.\w+)*\b(?=\s+\w+)/,inside:{punctuation:/\./}},{pattern:/(\[)[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}},{pattern:/(\b(?:class|interface)\s+[A-Z]\w*(?:\.\w+)*\s*:\s*)[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}},{pattern:/((?:\b(?:class|interface|new)\s+)|(?:catch\s+\())[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}}],number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)f?/i,operator:/>>=?|<<=?|[-=]>|([-+&|?])\1|~|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),Prism.languages.insertBefore("csharp","class-name",{"generic-method":{pattern:/\w+\s*<[^>\r\n]+?>\s*(?=\()/,inside:{function:/^\w+/,"class-name":{pattern:/\b[A-Z]\w*(?:\.\w+)*\b/,inside:{punctuation:/\./}},keyword:Prism.languages.csharp.keyword,punctuation:/[<>(),.:]/}},preprocessor:{pattern:/(^\s*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(\s*#)\b(?:define|elif|else|endif|endregion|error|if|line|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}}),Prism.languages.dotnet=Prism.languages.cs=Prism.languages.csharp; 11 | Prism.languages.aspnet=Prism.languages.extend("markup",{"page-directive":{pattern:/<%\s*@.*%>/i,alias:"tag",inside:{"page-directive":{pattern:/<%\s*@\s*(?:Assembly|Control|Implements|Import|Master(?:Type)?|OutputCache|Page|PreviousPageType|Reference|Register)?|%>/i,alias:"tag"},rest:Prism.languages.markup.tag.inside}},directive:{pattern:/<%.*%>/i,alias:"tag",inside:{directive:{pattern:/<%\s*?[$=%#:]{0,2}|%>/i,alias:"tag"},rest:Prism.languages.csharp}}}),Prism.languages.aspnet.tag.pattern=/<(?!%)\/?[^\s>\/]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,Prism.languages.insertBefore("inside","punctuation",{directive:Prism.languages.aspnet.directive},Prism.languages.aspnet.tag.inside["attr-value"]),Prism.languages.insertBefore("aspnet","comment",{"asp-comment":{pattern:/<%--[\s\S]*?--%>/,alias:["asp","comment"]}}),Prism.languages.insertBefore("aspnet",Prism.languages.javascript?"script":"tag",{"asp-script":{pattern:/()[\s\S]*?(?=<\/script>)/i,lookbehind:!0,alias:["asp","script"],inside:Prism.languages.csharp||{}}}); 12 | !function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)\w+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b\w+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+?)\s*(?:\r?\n|\r)[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s*(?:\r?\n|\r)[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0},{pattern:/(["'])(?:\\[\s\S]|\$\([^)]+\)|`[^`]+`|(?!\1)[^\\])*\1/,greedy:!0,inside:n}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|==?|!=?|=~|<<[<-]?|[&\d]?>>|\d?[<>]&?|&[>&]?|\|[&|]?|<=?|>=?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}};for(var a=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],r=n.variable[1].inside,s=0;s?^_ +\-.A-Z\d])*"/i,greedy:!0},number:/(?:\b\d+\.?\d*|\B\.\d+)(?:E[+-]?\d+)?/i,keyword:/\b(?:AS|BEEP|BLOAD|BSAVE|CALL(?: ABSOLUTE)?|CASE|CHAIN|CHDIR|CLEAR|CLOSE|CLS|COM|COMMON|CONST|DATA|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DIM|DO|DOUBLE|ELSE|ELSEIF|END|ENVIRON|ERASE|ERROR|EXIT|FIELD|FILES|FOR|FUNCTION|GET|GOSUB|GOTO|IF|INPUT|INTEGER|IOCTL|KEY|KILL|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|MKDIR|NAME|NEXT|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPTION BASE|OUT|POKE|PUT|READ|REDIM|REM|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SHARED|SINGLE|SELECT CASE|SHELL|SLEEP|STATIC|STEP|STOP|STRING|SUB|SWAP|SYSTEM|THEN|TIMER|TO|TROFF|TRON|TYPE|UNLOCK|UNTIL|USING|VIEW PRINT|WAIT|WEND|WHILE|WRITE)(?:\$|\b)/i,function:/\b(?:ABS|ACCESS|ACOS|ANGLE|AREA|ARITHMETIC|ARRAY|ASIN|ASK|AT|ATN|BASE|BEGIN|BREAK|CAUSE|CEIL|CHR|CLIP|COLLATE|COLOR|CON|COS|COSH|COT|CSC|DATE|DATUM|DEBUG|DECIMAL|DEF|DEG|DEGREES|DELETE|DET|DEVICE|DISPLAY|DOT|ELAPSED|EPS|ERASABLE|EXLINE|EXP|EXTERNAL|EXTYPE|FILETYPE|FIXED|FP|GO|GRAPH|HANDLER|IDN|IMAGE|IN|INT|INTERNAL|IP|IS|KEYED|LBOUND|LCASE|LEFT|LEN|LENGTH|LET|LINE|LINES|LOG|LOG10|LOG2|LTRIM|MARGIN|MAT|MAX|MAXNUM|MID|MIN|MISSING|MOD|NATIVE|NUL|NUMERIC|OF|OPTION|ORD|ORGANIZATION|OUTIN|OUTPUT|PI|POINT|POINTER|POINTS|POS|PRINT|PROGRAM|PROMPT|RAD|RADIANS|RANDOMIZE|RECORD|RECSIZE|RECTYPE|RELATIVE|REMAINDER|REPEAT|REST|RETRY|REWRITE|RIGHT|RND|ROUND|RTRIM|SAME|SEC|SELECT|SEQUENTIAL|SET|SETTER|SGN|SIN|SINH|SIZE|SKIP|SQR|STANDARD|STATUS|STR|STREAM|STYLE|TAB|TAN|TANH|TEMPLATE|TEXT|THERE|TIME|TIMEOUT|TRACE|TRANSFORM|TRUNCATE|UBOUND|UCASE|USE|VAL|VARIABLE|VIEWPORT|WHEN|WINDOW|WITH|ZER|ZONEWIDTH)(?:\$|\b)/i,operator:/<[=>]?|>=?|[+\-*\/^=&]|\b(?:AND|EQV|IMP|NOT|OR|XOR)\b/i,punctuation:/[,;:()]/}; 14 | !function(e){var r=/%%?[~:\w]+%?|!\S+!/,t={pattern:/\/[a-z?]+(?=[ :]|$):?|-[a-z]\b|--[a-z-]+\b/im,alias:"attr-name",inside:{punctuation:/:/}},n=/"[^"]*"/,i=/(?:\b|-)\d+\b/;Prism.languages.batch={comment:[/^::.*/m,{pattern:/((?:^|[&(])[ \t]*)rem\b(?:[^^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0}],label:{pattern:/^:.*/m,alias:"property"},command:[{pattern:/((?:^|[&(])[ \t]*)for(?: ?\/[a-z?](?:[ :](?:"[^"]*"|\S+))?)* \S+ in \([^)]+\) do/im,lookbehind:!0,inside:{keyword:/^for\b|\b(?:in|do)\b/i,string:n,parameter:t,variable:r,number:i,punctuation:/[()',]/}},{pattern:/((?:^|[&(])[ \t]*)if(?: ?\/[a-z?](?:[ :](?:"[^"]*"|\S+))?)* (?:not )?(?:cmdextversion \d+|defined \w+|errorlevel \d+|exist \S+|(?:"[^"]*"|\S+)?(?:==| (?:equ|neq|lss|leq|gtr|geq) )(?:"[^"]*"|\S+))/im,lookbehind:!0,inside:{keyword:/^if\b|\b(?:not|cmdextversion|defined|errorlevel|exist)\b/i,string:n,parameter:t,variable:r,number:i,operator:/\^|==|\b(?:equ|neq|lss|leq|gtr|geq)\b/i}},{pattern:/((?:^|[&()])[ \t]*)else\b/im,lookbehind:!0,inside:{keyword:/^else\b/i}},{pattern:/((?:^|[&(])[ \t]*)set(?: ?\/[a-z](?:[ :](?:"[^"]*"|\S+))?)* (?:[^^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0,inside:{keyword:/^set\b/i,string:n,parameter:t,variable:[r,/\w+(?=(?:[*\/%+\-&^|]|<<|>>)?=)/],number:i,operator:/[*\/%+\-&^|]=?|<<=?|>>=?|[!~_=]/,punctuation:/[()',]/}},{pattern:/((?:^|[&(])[ \t]*@?)\w+\b(?:[^^&)\r\n]|\^(?:\r\n|[\s\S]))*/im,lookbehind:!0,inside:{keyword:/^\w+\b/i,string:n,parameter:t,label:{pattern:/(^\s*):\S+/m,lookbehind:!0,alias:"property"},variable:r,number:i,operator:/\^/}}],operator:/[&@]/,punctuation:/[()']/}}(); 15 | Prism.languages.c=Prism.languages.extend("clike",{"class-name":{pattern:/(\b(?:enum|struct)\s+)\w+/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/,number:/(?:\b0x(?:[\da-f]+\.?[\da-f]*|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?)[ful]*/i}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+(?:[^\r\n\\]|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,alias:"property",inside:{string:{pattern:/(#\s*include\s*)(?:<.+?>|("|')(?:\\?.)+?\2)/,lookbehind:!0},directive:{pattern:/(#\s*)\b(?:define|defined|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,lookbehind:!0,alias:"keyword"}}},constant:/\b(?:__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/}),delete Prism.languages.c.boolean; 16 | Prism.languages.cpp=Prism.languages.extend("c",{"class-name":{pattern:/(\b(?:class|enum|struct)\s+)\w+/,lookbehind:!0},keyword:/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+\.?[\da-f']*|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+\.?[\d']*|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]*/i,greedy:!0},operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:true|false)\b/}),Prism.languages.insertBefore("cpp","string",{"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}); 17 | !function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},rest:e.languages.javascript}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(Prism); 18 | Prism.languages.d=Prism.languages.extend("clike",{comment:[{pattern:/^\s*#!.+/,greedy:!0},{pattern:RegExp("(^|[^\\\\])(?:"+["\\/\\+(?:\\/\\+[\\s\\S]*?\\+\\/|(?!\\/\\+)[\\s\\S])*?\\+\\/","\\/\\/.*","\\/\\*[\\s\\S]*?\\*\\/"].join("|")+")"),lookbehind:!0,greedy:!0}],string:[{pattern:RegExp(['\\b[rx]"(?:\\\\[\\s\\S]|[^\\\\"])*"[cwd]?','\\bq"(?:\\[[\\s\\S]*?\\]|\\([\\s\\S]*?\\)|<[\\s\\S]*?>|\\{[\\s\\S]*?\\})"','\\bq"((?!\\d)\\w+)$[\\s\\S]*?^\\1"','\\bq"(.)[\\s\\S]*?\\2"',"'(?:\\\\(?:\\W|\\w+)|[^\\\\])'",'(["`])(?:\\\\[\\s\\S]|(?!\\3)[^\\\\])*\\3[cwd]?'].join("|"),"m"),greedy:!0},{pattern:/\bq\{(?:\{[^{}]*\}|[^{}])*\}/,greedy:!0,alias:"token-string"}],number:[/\b0x\.?[a-f\d_]+(?:(?!\.\.)\.[a-f\d_]*)?(?:p[+-]?[a-f\d_]+)?[ulfi]*/i,{pattern:/((?:\.\.)?)(?:\b0b\.?|\b|\.)\d[\d_]*(?:(?!\.\.)\.[\d_]*)?(?:e[+-]?\d[\d_]*)?[ulfi]*/i,lookbehind:!0}],keyword:/\$|\b(?:abstract|alias|align|asm|assert|auto|body|bool|break|byte|case|cast|catch|cdouble|cent|cfloat|char|class|const|continue|creal|dchar|debug|default|delegate|delete|deprecated|do|double|else|enum|export|extern|false|final|finally|float|for|foreach|foreach_reverse|function|goto|idouble|if|ifloat|immutable|import|inout|int|interface|invariant|ireal|lazy|long|macro|mixin|module|new|nothrow|null|out|override|package|pragma|private|protected|public|pure|real|ref|return|scope|shared|short|static|struct|super|switch|synchronized|template|this|throw|true|try|typedef|typeid|typeof|ubyte|ucent|uint|ulong|union|unittest|ushort|version|void|volatile|wchar|while|with|__(?:(?:FILE|MODULE|LINE|FUNCTION|PRETTY_FUNCTION|DATE|EOF|TIME|TIMESTAMP|VENDOR|VERSION)__|gshared|traits|vector|parameters)|string|wstring|dstring|size_t|ptrdiff_t)\b/,operator:/\|[|=]?|&[&=]?|\+[+=]?|-[-=]?|\.?\.\.|=[>=]?|!(?:i[ns]\b|<>?=?|>=?|=)?|\bi[ns]\b|(?:<[<>]?|>>?>?|\^\^|[*\/%^~])=?/}),Prism.languages.insertBefore("d","keyword",{property:/\B@\w*/}),Prism.languages.insertBefore("d","function",{register:{pattern:/\b(?:[ABCD][LHX]|E[ABCD]X|E?(?:BP|SP|DI|SI)|[ECSDGF]S|CR[0234]|DR[012367]|TR[3-7]|X?MM[0-7]|R[ABCD]X|[BS]PL|R[BS]P|[DS]IL|R[DS]I|R(?:[89]|1[0-5])[BWD]?|XMM(?:[89]|1[0-5])|YMM(?:1[0-5]|\d))\b|\bST(?:\([0-7]\)|\b)/,alias:"variable"}}); 19 | Prism.languages.dart=Prism.languages.extend("clike",{string:[{pattern:/r?("""|''')[\s\S]*?\1/,greedy:!0},{pattern:/r?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0}],keyword:[/\b(?:async|sync|yield)\*/,/\b(?:abstract|assert|async|await|break|case|catch|class|const|continue|default|deferred|do|dynamic|else|enum|export|external|extends|factory|final|finally|for|get|if|implements|import|in|library|new|null|operator|part|rethrow|return|set|static|super|switch|this|throw|try|typedef|var|void|while|with|yield)\b/],operator:/\bis!|\b(?:as|is)\b|\+\+|--|&&|\|\||<<=?|>>=?|~(?:\/=?)?|[+\-*\/%&^|=!<>]=?|\?/}),Prism.languages.insertBefore("dart","function",{metadata:{pattern:/@\w+/,alias:"symbol"}}); 20 | Prism.languages.erlang={comment:/%.+/,string:{pattern:/"(?:\\.|[^\\"\r\n])*"/,greedy:!0},"quoted-function":{pattern:/'(?:\\.|[^\\'\r\n])+'(?=\()/,alias:"function"},"quoted-atom":{pattern:/'(?:\\.|[^\\'\r\n])+'/,alias:"atom"},boolean:/\b(?:true|false)\b/,keyword:/\b(?:fun|when|case|of|end|if|receive|after|try|catch)\b/,number:[/\$\\?./,/\d+#[a-z0-9]+/i,/(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i],function:/\b[a-z][\w@]*(?=\()/,variable:{pattern:/(^|[^@])(?:\b|\?)[A-Z_][\w@]*/,lookbehind:!0},operator:[/[=\/<>:]=|=[:\/]=|\+\+?|--?|[=*\/!]|\b(?:bnot|div|rem|band|bor|bxor|bsl|bsr|not|and|or|xor|orelse|andalso)\b/,{pattern:/(^|[^<])<(?!<)/,lookbehind:!0},{pattern:/(^|[^>])>(?!>)/,lookbehind:!0}],atom:/\b[a-z][\w@]*/,punctuation:/[()[\]{}:;,.#|]|<<|>>/}; 21 | Prism.languages.fsharp=Prism.languages.extend("clike",{comment:[{pattern:/(^|[^\\])\(\*[\s\S]*?\*\)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(?:"""[\s\S]*?"""|@"(?:""|[^"])*"|"(?:\\[\s\S]|[^\\"])*")B?|'(?:[^\\']|\\(?:.|\d{3}|x[a-fA-F\d]{2}|u[a-fA-F\d]{4}|U[a-fA-F\d]{8}))'B?/,greedy:!0},"class-name":{pattern:/(\b(?:exception|inherit|interface|new|of|type)\s+|\w\s*:\s*|\s:\??>\s*)[.\w]+\b(?:\s*(?:->|\*)\s*[.\w]+\b)*(?!\s*[:.])/,lookbehind:!0,inside:{operator:/->|\*/,punctuation:/\./}},keyword:/\b(?:let|return|use|yield)(?:!\B|\b)|\b(?:abstract|and|as|assert|base|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|global|if|in|inherit|inline|interface|internal|lazy|match|member|module|mutable|namespace|new|not|null|of|open|or|override|private|public|rec|select|static|struct|then|to|true|try|type|upcast|val|void|when|while|with|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|include|method|mixin|object|parallel|process|protected|pure|sealed|tailcall|trait|virtual|volatile)\b/,number:[/\b0x[\da-fA-F]+(?:un|lf|LF)?\b/,/\b0b[01]+(?:y|uy)?\b/,/(?:\b\d+\.?\d*|\B\.\d+)(?:[fm]|e[+-]?\d+)?\b/i,/\b\d+(?:[IlLsy]|u[lsy]?|UL)?\b/],operator:/([<>~&^])\1\1|([*.:<>&])\2|<-|->|[!=:]=|?|\??(?:<=|>=|<>|[-+*/%=<>])\??|[!?^&]|~[+~-]|:>|:\?>?/}),Prism.languages.insertBefore("fsharp","keyword",{preprocessor:{pattern:/^[^\r\n\S]*#.*/m,alias:"property",inside:{directive:{pattern:/(\s*#)\b(?:else|endif|if|light|line|nowarn)\b/,lookbehind:!0,alias:"keyword"}}}}),Prism.languages.insertBefore("fsharp","punctuation",{"computation-expression":{pattern:/[_a-z]\w*(?=\s*\{)/i,alias:"keyword"}}),Prism.languages.insertBefore("fsharp","string",{annotation:{pattern:/\[<.+?>\]/,inside:{punctuation:/^\[<|>\]$/,"class-name":{pattern:/^\w+$|(^|;\s*)[A-Z]\w*(?=\()/,lookbehind:!0},"annotation-content":{pattern:/[\s\S]+/,inside:Prism.languages.fsharp}}}}); 22 | Prism.languages.go=Prism.languages.extend("clike",{keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,builtin:/\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/,boolean:/\b(?:_|iota|nil|true|false)\b/,operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,number:/(?:\b0x[a-f\d]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e[-+]?\d+)?)i?/i,string:{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0}}),delete Prism.languages.go["class-name"]; 23 | Prism.languages.graphql={comment:/#.*/,string:{pattern:/"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:true|false)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+)[a-zA-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:enum|fragment|implements|input|interface|mutation|on|query|scalar|schema|type|union)\b/,operator:/[!=|]|\.{3}/,punctuation:/[!(){}\[\]:=,]/,constant:/\b(?!ID\b)[A-Z][A-Z_\d]*\b/}; 24 | Prism.languages.groovy=Prism.languages.extend("clike",{string:[{pattern:/("""|''')(?:[^\\]|\\[\s\S])*?\1|\$\/(?:\$\/\$|[\s\S])*?\/\$/,greedy:!0},{pattern:/(["'/])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0}],keyword:/\b(?:as|def|in|abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|native|new|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|trait|transient|try|void|volatile|while)\b/,number:/\b(?:0b[01_]+|0x[\da-f_]+(?:\.[\da-f_p\-]+)?|[\d_]+(?:\.[\d_]+)?(?:e[+-]?[\d]+)?)[glidf]?\b/i,operator:{pattern:/(^|[^.])(?:~|==?~?|\?[.:]?|\*(?:[.=]|\*=?)?|\.[@&]|\.\.<|\.\.(?!\.)|-[-=>]?|\+[+=]?|!=?|<(?:<=?|=>?)?|>(?:>>?=?|=)?|&[&=]?|\|[|=]?|\/=?|\^=?|%=?)/,lookbehind:!0},punctuation:/\.+|[{}[\];(),.:$]/}),Prism.languages.insertBefore("groovy","string",{shebang:{pattern:/#!.+/,alias:"comment"}}),Prism.languages.insertBefore("groovy","punctuation",{"spock-block":/\b(?:setup|given|when|then|and|cleanup|expect|where):/}),Prism.languages.insertBefore("groovy","function",{annotation:{pattern:/(^|[^.])@\w+/,lookbehind:!0,alias:"punctuation"}}),Prism.hooks.add("wrap",function(e){if("groovy"===e.language&&"string"===e.type){var t=e.content[0];if("'"!=t){var n=/([^\\])(?:\$(?:\{.*?\}|[\w.]+))/;"$"===t&&(n=/([^\$])(?:\$(?:\{.*?\}|[\w.]+))/),e.content=e.content.replace(/</g,"<").replace(/&/g,"&"),e.content=Prism.highlight(e.content,{expression:{pattern:n,lookbehind:!0,inside:Prism.languages.groovy}}),e.classes.push("/"===t?"regex":"gstring")}}}); 25 | !function(h){function v(e,n){return"___"+e.toUpperCase()+n+"___"}Object.defineProperties(h.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,r,e,o){if(a.language===r){var c=a.tokenStack=[];a.code=a.code.replace(e,function(e){if("function"==typeof o&&!o(e))return e;for(var n,t=c.length;-1!==a.code.indexOf(n=v(r,t));)++t;return c[t]=e,n}),a.grammar=h.languages.markup}}},tokenizePlaceholders:{value:function(p,k){if(p.language===k&&p.tokenStack){p.grammar=h.languages[k];var m=0,d=Object.keys(p.tokenStack);!function e(n){for(var t=0;t=d.length);t++){var a=n[t];if("string"==typeof a||a.content&&"string"==typeof a.content){var r=d[m],o=p.tokenStack[r],c="string"==typeof a?a:a.content,i=v(k,r),u=c.indexOf(i);if(-1@\[\\\]^`{|}~]/,variable:/[^!"#%&'()*+,\/;<=>@\[\\\]^`{|}~\s]+/},e.hooks.add("before-tokenize",function(a){e.languages["markup-templating"].buildPlaceholders(a,"handlebars",/\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/g)}),e.hooks.add("after-tokenize",function(a){e.languages["markup-templating"].tokenizePlaceholders(a,"handlebars")})}(Prism); 27 | !function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|null|open|opens|package|private|protected|provides|public|requires|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,a=/\b[A-Z](?:\w*[a-z]\w*)?\b/;e.languages.java=e.languages.extend("clike",{"class-name":[a,/\b[A-Z]\w*(?=\s+\w+\s*[;,=())])/],keyword:t,function:[e.languages.clike.function,{pattern:/(\:\:)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x[\da-f_]*\.?[\da-f_p+-]+\b|(?:\b\d[\d_]*\.?[\d_]*|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0}}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"}}),e.languages.insertBefore("java","class-name",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0},namespace:{pattern:/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)[a-z]\w*(?:\.[a-z]\w*)+/,lookbehind:!0,inside:{punctuation:/\./}},generics:{pattern:/<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<[\w\s,.&?]*>)*>)*>)*>/,inside:{"class-name":a,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}}})}(Prism); 28 | Prism.languages.json={property:{pattern:/"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,greedy:!0},string:{pattern:/"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,greedy:!0},comment:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,number:/-?\d+\.?\d*(?:e[+-]?\d+)?/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}}; 29 | !function(a){var e=/\\(?:[^a-z()[\]]|[a-z*]+)/i,n={"equation-command":{pattern:e,alias:"regex"}};a.languages.latex={comment:/%.*/m,cdata:{pattern:/(\\begin\{((?:verbatim|lstlisting)\*?)\})[\s\S]*?(?=\\end\{\2\})/,lookbehind:!0},equation:[{pattern:/\$\$(?:\\[\s\S]|[^\\$])+\$\$|\$(?:\\[\s\S]|[^\\$])+\$|\\\([\s\S]*?\\\)|\\\[[\s\S]*?\\\]/,inside:n,alias:"string"},{pattern:/(\\begin\{((?:equation|math|eqnarray|align|multline|gather)\*?)\})[\s\S]*?(?=\\end\{\2\})/,lookbehind:!0,inside:n,alias:"string"}],keyword:{pattern:/(\\(?:begin|end|ref|cite|label|usepackage|documentclass)(?:\[[^\]]+\])?\{)[^}]+(?=\})/,lookbehind:!0},url:{pattern:/(\\url\{)[^}]+(?=\})/,lookbehind:!0},headline:{pattern:/(\\(?:part|chapter|section|subsection|frametitle|subsubsection|paragraph|subparagraph|subsubparagraph|subsubsubparagraph)\*?(?:\[[^\]]+\])?\{)[^}]+(?=\}(?:\[[^\]]+\])?)/,lookbehind:!0,alias:"class-name"},function:{pattern:e,alias:"selector"},punctuation:/[[\]{}&]/},a.languages.tex=a.languages.latex,a.languages.context=a.languages.latex}(Prism); 30 | Prism.languages.less=Prism.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-]+?(?:\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};])*?(?=\s*\{)/,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};@])*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/i,operator:/[+\-*\/]/}),Prism.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-]+.*?(?=[(;])/,lookbehind:!0,alias:"function"}}); 31 | Prism.languages.livescript={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\])#.*/,lookbehind:!0}],"interpolated-string":{pattern:/(^|[^"])("""|")(?:\\[\s\S]|(?!\2)[^\\])*\2(?!")/,lookbehind:!0,greedy:!0,inside:{variable:{pattern:/(^|[^\\])#[a-z_](?:-?[a-z]|[\d_])*/m,lookbehind:!0},interpolation:{pattern:/(^|[^\\])#\{[^}]+\}/m,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^#\{|\}$/,alias:"variable"}}},string:/[\s\S]+/}},string:[{pattern:/('''|')(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},{pattern:/<\[[\s\S]*?\]>/,greedy:!0},/\\[^\s,;\])}]+/],regex:[{pattern:/\/\/(?:\[.+?]|\\.|(?!\/\/)[^\\])+\/\/[gimyu]{0,5}/,greedy:!0,inside:{comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0}}},{pattern:/\/(?:\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}/,greedy:!0}],keyword:{pattern:/(^|(?!-).)\b(?:break|case|catch|class|const|continue|default|do|else|extends|fallthrough|finally|for(?: ever)?|function|if|implements|it|let|loop|new|null|otherwise|own|return|super|switch|that|then|this|throw|try|unless|until|var|void|when|while|yield)(?!-)\b/m,lookbehind:!0},"keyword-operator":{pattern:/(^|[^-])\b(?:(?:delete|require|typeof)!|(?:and|by|delete|export|from|import(?: all)?|in|instanceof|is(?:nt| not)?|not|of|or|til|to|typeof|with|xor)(?!-)\b)/m,lookbehind:!0,alias:"operator"},boolean:{pattern:/(^|[^-])\b(?:false|no|off|on|true|yes)(?!-)\b/m,lookbehind:!0},argument:{pattern:/(^|(?!\.&\.)[^&])&(?!&)\d*/m,lookbehind:!0,alias:"variable"},number:/\b(?:\d+~[\da-z]+|\d[\d_]*(?:\.\d[\d_]*)?(?:[a-z]\w*)?)/i,identifier:/[a-z_](?:-?[a-z]|[\d_])*/i,operator:[{pattern:/( )\.(?= )/,lookbehind:!0},/\.(?:[=~]|\.\.?)|\.(?:[&|^]|<<|>>>?)\.|:(?:=|:=?)|&&|\|[|>]|<(?:<[>=?]?|-(?:->?|>)?|\+\+?|@@?|%%?|\*\*?|!(?:~?=|--?>|~?~>)?|~(?:~?>|=)?|==?|\^\^?|[\/?]/],punctuation:/[(){}\[\]|.,:;`]/},Prism.languages.livescript["interpolated-string"].inside.interpolation.inside.rest=Prism.languages.livescript; 32 | Prism.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[\s\S]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+\.?[a-f\d]*(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|\.?\d*(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/}; 33 | Prism.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},builtin:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,symbol:{pattern:/^[^:=\r\n]+(?=\s*:(?!=))/m,inside:{variable:/\$+(?:[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:[/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,{pattern:/(\()(?:addsuffix|abspath|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:s|list)?)(?=[ \t])/,lookbehind:!0}],operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/}; 34 | !function(d){function n(n,e){return n=n.replace(//g,"(?:\\\\.|[^\\\\\\n\r]|(?:\r?\n|\r)(?!\r?\n|\r))"),e&&(n=n+"|"+n.replace(/_/g,"\\*")),RegExp("((?:^|[^\\\\])(?:\\\\{2})*)(?:"+n+")")}var e="(?:\\\\.|``.+?``|`[^`\r\\n]+`|[^\\\\|\r\\n`])+",t="\\|?__(?:\\|__)+\\|?(?:(?:\r?\n|\r)|$)".replace(/__/g,e),a="\\|?[ \t]*:?-{3,}:?[ \t]*(?:\\|[ \t]*:?-{3,}:?[ \t]*)+\\|?(?:\r?\n|\r)";d.languages.markdown=d.languages.extend("markup",{}),d.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+t+a+"(?:"+t+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+t+a+")(?:"+t+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(e),inside:d.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+t+")"+a+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+t+"$"),inside:{"table-header":{pattern:RegExp(e),alias:"important",inside:d.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/(^[ \t]*(?:\r?\n|\r))(?: {4}|\t).+(?:(?:\r?\n|\r)(?: {4}|\t).+)*/m,lookbehind:!0,alias:"keyword"},{pattern:/``.+?``|`[^`\r\n]+`/,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\r?\n|\r))[\s\S]+?(?=(?:\r?\n|\r)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\r?\n|\r)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n("__(?:(?!_)|_(?:(?!_))+_)+__",!0),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n("_(?:(?!_)|__(?:(?!_))+__)+_",!0),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n("(~~?)(?:(?!~))+?\\2",!1),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},url:{pattern:n('!?\\[(?:(?!\\]))+\\](?:\\([^\\s)]+(?:[\t ]+"(?:\\\\.|[^"\\\\])*")?\\)| ?\\[(?:(?!\\]))+\\])',!1),lookbehind:!0,greedy:!0,inside:{variable:{pattern:/(\[)[^\]]+(?=\]$)/,lookbehind:!0},content:{pattern:/(^!?\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),["url","bold","italic","strike"].forEach(function(e){["url","bold","italic","strike"].forEach(function(n){e!==n&&(d.languages.markdown[e].inside.content.inside[n]=d.languages.markdown[n])})}),d.hooks.add("after-tokenize",function(n){"markdown"!==n.language&&"md"!==n.language||!function n(e){if(e&&"string"!=typeof e)for(var t=0,a=e.length;t]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete Prism.languages.objectivec["class-name"]; 37 | Prism.languages.pascal={comment:[/\(\*[\s\S]+?\*\)/,/\{[\s\S]+?\}/,/\/\/.*/],string:{pattern:/(?:'(?:''|[^'\r\n])*'|#[&$%]?[a-f\d]+)+|\^[a-z]/i,greedy:!0},keyword:[{pattern:/(^|[^&])\b(?:absolute|array|asm|begin|case|const|constructor|destructor|do|downto|else|end|file|for|function|goto|if|implementation|inherited|inline|interface|label|nil|object|of|operator|packed|procedure|program|record|reintroduce|repeat|self|set|string|then|to|type|unit|until|uses|var|while|with)\b/i,lookbehind:!0},{pattern:/(^|[^&])\b(?:dispose|exit|false|new|true)\b/i,lookbehind:!0},{pattern:/(^|[^&])\b(?:class|dispinterface|except|exports|finalization|finally|initialization|inline|library|on|out|packed|property|raise|resourcestring|threadvar|try)\b/i,lookbehind:!0},{pattern:/(^|[^&])\b(?:absolute|abstract|alias|assembler|bitpacked|break|cdecl|continue|cppdecl|cvar|default|deprecated|dynamic|enumerator|experimental|export|external|far|far16|forward|generic|helper|implements|index|interrupt|iochecks|local|message|name|near|nodefault|noreturn|nostackframe|oldfpccall|otherwise|overload|override|pascal|platform|private|protected|public|published|read|register|reintroduce|result|safecall|saveregisters|softfloat|specialize|static|stdcall|stored|strict|unaligned|unimplemented|varargs|virtual|write)\b/i,lookbehind:!0}],number:[/(?:[&%]\d+|\$[a-f\d]+)/i,/\b\d+(?:\.\d+)?(?:e[+-]?\d+)?/i],operator:[/\.\.|\*\*|:=|<[<=>]?|>[>=]?|[+\-*\/]=?|[@^=]/i,{pattern:/(^|[^&])\b(?:and|as|div|exclude|in|include|is|mod|not|or|shl|shr|xor)\b/,lookbehind:!0}],punctuation:/\(\.|\.\)|[()\[\]:;,.]/},Prism.languages.objectpascal=Prism.languages.pascal; 38 | Prism.languages.perl={comment:[{pattern:/(^\s*)=\w+[\s\S]*?=cut.*/m,lookbehind:!0},{pattern:/(^|[^\\$])#.*/,lookbehind:!0}],string:[{pattern:/\b(?:q|qq|qx|qw)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\{(?:[^{}\\]|\\[\s\S])*\}/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\[(?:[^[\]\\]|\\[\s\S])*\]/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0},{pattern:/("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/'(?:[^'\\\r\n]|\\.)*'/,greedy:!0}],regex:[{pattern:/\b(?:m|qr)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngc]*/,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s+([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\((?:[^()\\]|\\[\s\S])*\)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\{(?:[^{}\\]|\\[\s\S])*\}\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\[(?:[^[\]\\]|\\[\s\S])*\]\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*<(?:[^<>\\]|\\[\s\S])*>\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/\/(?:[^\/\\\r\n]|\\.)*\/[msixpodualngc]*(?=\s*(?:$|[\r\n,.;})&|\-+*~<>!?^]|(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor|x)\b))/,greedy:!0}],variable:[/[&*$@%]\{\^[A-Z]+\}/,/[&*$@%]\^[A-Z_]/,/[&*$@%]#?(?=\{)/,/[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+)+(?:::)*/i,/[&*$@%]\d+/,/(?!%=)[$@%][!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~]/],filehandle:{pattern:/<(?![<=])\S*>|\b_\b/,alias:"symbol"},vstring:{pattern:/v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,alias:"string"},function:{pattern:/sub [a-z0-9_]+/i,inside:{keyword:/sub/}},keyword:/\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|return|say|state|sub|switch|undef|unless|until|use|when|while)\b/,number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,operator:/-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,punctuation:/[{}[\];(),:]/}; 39 | !function(n){n.languages.php=n.languages.extend("clike",{keyword:/\b(?:__halt_compiler|abstract|and|array|as|break|callable|case|catch|class|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|eval|exit|extends|final|finally|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|namespace|new|or|parent|print|private|protected|public|require|require_once|return|static|switch|throw|trait|try|unset|use|var|while|xor|yield)\b/i,boolean:{pattern:/\b(?:false|true)\b/i,alias:"constant"},constant:[/\b[A-Z_][A-Z0-9_]*\b/,/\b(?:null)\b/i],comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0}}),n.languages.insertBefore("php","string",{"shell-comment":{pattern:/(^|[^\\])#.*/,lookbehind:!0,alias:"comment"}}),n.languages.insertBefore("php","comment",{delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"}}),n.languages.insertBefore("php","keyword",{variable:/\$+(?:\w+\b|(?={))/i,package:{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/,lookbehind:!0,inside:{punctuation:/\\/}}}),n.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/,lookbehind:!0}});var e={pattern:/{\$(?:{(?:{[^{}]+}|[^{}]+)}|[^{}])+}|(^|[^\\{])\$+(?:\w+(?:\[.+?]|->\w+)*)/,lookbehind:!0,inside:n.languages.php};n.languages.insertBefore("php","string",{"nowdoc-string":{pattern:/<<<'([^']+)'(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;/,greedy:!0,alias:"string",inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},"heredoc-string":{pattern:/<<<(?:"([^"]+)"(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;|([a-z_]\w*)(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\2;)/i,greedy:!0,alias:"string",inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:e}},"single-quoted-string":{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0,alias:"string"},"double-quoted-string":{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,alias:"string",inside:{interpolation:e}}}),delete n.languages.php.string,n.hooks.add("before-tokenize",function(e){if(/<\?/.test(e.code)){n.languages["markup-templating"].buildPlaceholders(e,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#)(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|\/\*[\s\S]*?(?:\*\/|$))*?(?:\?>|$)/gi)}}),n.hooks.add("after-tokenize",function(e){n.languages["markup-templating"].tokenizePlaceholders(e,"php")})}(Prism); 40 | !function(e){var t=Prism.languages.powershell={comment:[{pattern:/(^|[^`])<#[\s\S]*?#>/,lookbehind:!0},{pattern:/(^|[^`])#.*/,lookbehind:!0}],string:[{pattern:/"(?:`[\s\S]|[^`"])*"/,greedy:!0,inside:{function:{pattern:/(^|[^`])\$\((?:\$\(.*?\)|(?!\$\()[^\r\n)])*\)/,lookbehind:!0,inside:{}}}},{pattern:/'(?:[^']|'')*'/,greedy:!0}],namespace:/\[[a-z](?:\[(?:\[[^\]]*]|[^\[\]])*]|[^\[\]])*]/i,boolean:/\$(?:true|false)\b/i,variable:/\$\w+\b/i,function:[/\b(?:Add-(?:Computer|Content|History|Member|PSSnapin|Type)|Checkpoint-Computer|Clear-(?:Content|EventLog|History|Item|ItemProperty|Variable)|Compare-Object|Complete-Transaction|Connect-PSSession|ConvertFrom-(?:Csv|Json|StringData)|Convert-Path|ConvertTo-(?:Csv|Html|Json|Xml)|Copy-(?:Item|ItemProperty)|Debug-Process|Disable-(?:ComputerRestore|PSBreakpoint|PSRemoting|PSSessionConfiguration)|Disconnect-PSSession|Enable-(?:ComputerRestore|PSBreakpoint|PSRemoting|PSSessionConfiguration)|Enter-PSSession|Exit-PSSession|Export-(?:Alias|Clixml|Console|Csv|FormatData|ModuleMember|PSSession)|ForEach-Object|Format-(?:Custom|List|Table|Wide)|Get-(?:Alias|ChildItem|Command|ComputerRestorePoint|Content|ControlPanelItem|Culture|Date|Event|EventLog|EventSubscriber|FormatData|Help|History|Host|HotFix|Item|ItemProperty|Job|Location|Member|Module|Process|PSBreakpoint|PSCallStack|PSDrive|PSProvider|PSSession|PSSessionConfiguration|PSSnapin|Random|Service|TraceSource|Transaction|TypeData|UICulture|Unique|Variable|WmiObject)|Group-Object|Import-(?:Alias|Clixml|Csv|LocalizedData|Module|PSSession)|Invoke-(?:Command|Expression|History|Item|RestMethod|WebRequest|WmiMethod)|Join-Path|Limit-EventLog|Measure-(?:Command|Object)|Move-(?:Item|ItemProperty)|New-(?:Alias|Event|EventLog|Item|ItemProperty|Module|ModuleManifest|Object|PSDrive|PSSession|PSSessionConfigurationFile|PSSessionOption|PSTransportOption|Service|TimeSpan|Variable|WebServiceProxy)|Out-(?:Default|File|GridView|Host|Null|Printer|String)|Pop-Location|Push-Location|Read-Host|Receive-(?:Job|PSSession)|Register-(?:EngineEvent|ObjectEvent|PSSessionConfiguration|WmiEvent)|Remove-(?:Computer|Event|EventLog|Item|ItemProperty|Job|Module|PSBreakpoint|PSDrive|PSSession|PSSnapin|TypeData|Variable|WmiObject)|Rename-(?:Computer|Item|ItemProperty)|Reset-ComputerMachinePassword|Resolve-Path|Restart-(?:Computer|Service)|Restore-Computer|Resume-(?:Job|Service)|Save-Help|Select-(?:Object|String|Xml)|Send-MailMessage|Set-(?:Alias|Content|Date|Item|ItemProperty|Location|PSBreakpoint|PSDebug|PSSessionConfiguration|Service|StrictMode|TraceSource|Variable|WmiInstance)|Show-(?:Command|ControlPanelItem|EventLog)|Sort-Object|Split-Path|Start-(?:Job|Process|Service|Sleep|Transaction)|Stop-(?:Computer|Job|Process|Service)|Suspend-(?:Job|Service)|Tee-Object|Test-(?:ComputerSecureChannel|Connection|ModuleManifest|Path|PSSessionConfigurationFile)|Trace-Command|Unblock-File|Undo-Transaction|Unregister-(?:Event|PSSessionConfiguration)|Update-(?:FormatData|Help|List|TypeData)|Use-Transaction|Wait-(?:Event|Job|Process)|Where-Object|Write-(?:Debug|Error|EventLog|Host|Output|Progress|Verbose|Warning))\b/i,/\b(?:ac|cat|chdir|clc|cli|clp|clv|compare|copy|cp|cpi|cpp|cvpa|dbp|del|diff|dir|ebp|echo|epal|epcsv|epsn|erase|fc|fl|ft|fw|gal|gbp|gc|gci|gcs|gdr|gi|gl|gm|gp|gps|group|gsv|gu|gv|gwmi|iex|ii|ipal|ipcsv|ipsn|irm|iwmi|iwr|kill|lp|ls|measure|mi|mount|move|mp|mv|nal|ndr|ni|nv|ogv|popd|ps|pushd|pwd|rbp|rd|rdr|ren|ri|rm|rmdir|rni|rnp|rp|rv|rvpa|rwmi|sal|saps|sasv|sbp|sc|select|set|shcm|si|sl|sleep|sls|sort|sp|spps|spsv|start|sv|swmi|tee|trcm|type|write)\b/i],keyword:/\b(?:Begin|Break|Catch|Class|Continue|Data|Define|Do|DynamicParam|Else|ElseIf|End|Exit|Filter|Finally|For|ForEach|From|Function|If|InlineScript|Parallel|Param|Process|Return|Sequence|Switch|Throw|Trap|Try|Until|Using|Var|While|Workflow)\b/i,operator:{pattern:/(\W?)(?:!|-(?:eq|ne|gt|ge|lt|le|sh[lr]|not|b?(?:and|x?or)|(?:Not)?(?:Like|Match|Contains|In)|Replace|Join|is(?:Not)?|as)\b|-[-=]?|\+[+=]?|[*\/%]=?)/i,lookbehind:!0},punctuation:/[|{}[\];(),.]/},o=t.string[0].inside;o.boolean=t.boolean,o.variable=t.variable,o.function.inside=t}(); 41 | Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]+?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]+?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^\s*)@\w+(?:\.\w+)*/im,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; 42 | Prism.languages.r={comment:/#.*/,string:{pattern:/(['"])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},"percent-operator":{pattern:/%[^%\s]*%/,alias:"operator"},boolean:/\b(?:TRUE|FALSE)\b/,ellipsis:/\.\.(?:\.|\d+)/,number:[/\b(?:NaN|Inf)\b/,/(?:\b0x[\dA-Fa-f]+(?:\.\d*)?|\b\d+\.?\d*|\B\.\d+)(?:[EePp][+-]?\d+)?[iL]?/],keyword:/\b(?:if|else|repeat|while|function|for|in|next|break|NULL|NA|NA_integer_|NA_real_|NA_complex_|NA_character_)\b/,operator:/->?>?|<(?:=|=!]=?|::?|&&?|\|\|?|[+*\/^$@~]/,punctuation:/[(){}\[\],;]/}; 43 | !function(e){e.languages.ruby=e.languages.extend("clike",{comment:[/#.*/,{pattern:/^=begin\s[\s\S]*?^=end/m,greedy:!0}],"class-name":{pattern:/(\b(?:class)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|protected|private|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/});var n={pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"tag"},rest:e.languages.ruby}};delete e.languages.ruby.function,e.languages.insertBefore("ruby","keyword",{regex:[{pattern:/%r([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\((?:[^()\\]|\\[\s\S])*\)[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r\[(?:[^\[\]\\]|\\[\s\S])*\][gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/%r<(?:[^<>\\]|\\[\s\S])*>[gim]{0,3}/,greedy:!0,inside:{interpolation:n}},{pattern:/(^|[^/])\/(?!\/)(?:\[.+?]|\\.|[^/\\\r\n])+\/[gim]{0,3}(?=\s*(?:$|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:{pattern:/(^|[^:]):[a-zA-Z_]\w*(?:[?!]|\b)/,lookbehind:!0},"method-definition":{pattern:/(\bdef\s+)[\w.]+/,lookbehind:!0,inside:{function:/\w+$/,rest:e.languages.ruby}}}),e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|Fixnum|Float|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,constant:/\b[A-Z]\w*(?:[?!]|\b)/}),e.languages.ruby.string=[{pattern:/%[qQiIwWxs]?([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?\[(?:[^\[\]\\]|\\[\s\S])*\]/,greedy:!0,inside:{interpolation:n}},{pattern:/%[qQiIwWxs]?<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0,inside:{interpolation:n}},{pattern:/("|')(?:#\{[^}]+\}|\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:n}}],e.languages.rb=e.languages.ruby}(Prism); 44 | Prism.languages.rust={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:[{pattern:/b?r(#*)"(?:\\.|(?!"\1)[^\\\r\n])*"\1/,greedy:!0},{pattern:/b?"(?:\\.|[^\\\r\n"])*"/,greedy:!0}],char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u{(?:[\da-fA-F]_*){1,6}|.)|[^\\\r\n\t'])'/,alias:"string"},"lifetime-annotation":{pattern:/'[^\s>']+/,alias:"symbol"},keyword:/\b(?:abstract|alignof|as|async|await|be|box|break|const|continue|crate|do|dyn|else|enum|extern|false|final|fn|for|if|impl|in|let|loop|match|mod|move|mut|offsetof|once|override|priv|pub|pure|ref|return|sizeof|static|self|Self|struct|super|true|trait|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,attribute:{pattern:/#!?\[.+?\]/,greedy:!0,alias:"attr-name"},function:[/\w+(?=\s*\()/,/\w+!(?=\s*\(|\[)/],"macro-rules":{pattern:/\w+!/,alias:"function"},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:[iu](?:8|16|32|64)?|f32|f64))?\b/,"closure-params":{pattern:/\|[^|]*\|(?=\s*[{-])/,inside:{punctuation:/[|:,]/,operator:/[&*]/}},punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<>?=?|[@?]/}; 45 | !function(e){e.languages.sass=e.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t]+.+)*/m,lookbehind:!0}}),e.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,inside:{atrule:/(?:@[\w-]+|[+=])/m}}}),delete e.languages.sass.atrule;var t=/\$[-\w]+|#\{\$[-\w]+\}/,a=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|or|not)\b/,{pattern:/(\s+)-(?=\s)/,lookbehind:!0}];e.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,inside:{punctuation:/:/,variable:t,operator:a}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s]+.*)/m,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:t,operator:a,important:e.languages.sass.important}}}),delete e.languages.sass.property,delete e.languages.sass.important,e.languages.insertBefore("sass","punctuation",{selector:{pattern:/([ \t]*)\S(?:,?[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,?[^,\r\n]+)*)*/,lookbehind:!0}})}(Prism); 46 | Prism.languages.scheme={comment:/;.*/,string:{pattern:/"(?:[^"\\]|\\.)*"|'[^()#'\s]+/,greedy:!0},character:{pattern:/#\\(?:[ux][a-fA-F\d]+|[a-zA-Z]+|\S)/,alias:"string"},keyword:{pattern:/(\()(?:define(?:-syntax|-library|-values)?|(?:case-)?lambda|let(?:\*|rec)?(?:-values)?|else|if|cond|begin|delay(?:-force)?|parameterize|guard|set!|(?:quasi-)?quote|syntax-rules)(?=[()\s])/,lookbehind:!0},builtin:{pattern:/(\()(?:(?:cons|car|cdr|list|call-with-current-continuation|call\/cc|append|abs|apply|eval)\b|null\?|pair\?|boolean\?|eof-object\?|char\?|procedure\?|number\?|port\?|string\?|vector\?|symbol\?|bytevector\?)(?=[()\s])/,lookbehind:!0},number:{pattern:/([\s()])[-+]?(?:\d+\/\d+|\d*\.?\d+(?:\s*[-+]\s*\d*\.?\d+i)?)\b/,lookbehind:!0},boolean:/#[tf]/,operator:{pattern:/(\()(?:[-+*%\/]|[<>]=?|=>?)(?=\s|$)/,lookbehind:!0},function:{pattern:/(\()[^()'\s]+(?=[()\s)]|$)/,lookbehind:!0},punctuation:/[()']/}; 47 | Prism.languages.smalltalk={comment:/"(?:""|[^"])*"/,character:{pattern:/\$./,alias:"string"},string:/'(?:''|[^'])*'/,symbol:/#[\da-z]+|#(?:-|([+\/\\*~<>=@%|&?!])\1?)|#(?=\()/i,"block-arguments":{pattern:/(\[\s*):[^\[|]*\|/,lookbehind:!0,inside:{variable:/:[\da-z]+/i,punctuation:/\|/}},"temporary-variables":{pattern:/\|[^|]+\|/,inside:{variable:/[\da-z]+/i,punctuation:/\|/}},keyword:/\b(?:nil|true|false|self|super|new)\b/,number:[/\d+r-?[\dA-Z]+(?:\.[\dA-Z]+)?(?:e-?\d+)?/,/\b\d+(?:\.\d+)?(?:e-?\d+)?/],operator:/[<=]=?|:=|~[~=]|\/\/?|\\\\|>[>=]?|[!^+\-*&|,@]/,punctuation:/[.;:?\[\](){}]/}; 48 | !function(n){n.languages.smarty={comment:/\{\*[\s\S]*?\*\}/,delimiter:{pattern:/^\{|\}$/i,alias:"punctuation"},string:/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,number:/\b0x[\dA-Fa-f]+|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][-+]?\d+)?/,variable:[/\$(?!\d)\w+/,/#(?!\d)\w+#/,{pattern:/(\.|->)(?!\d)\w+/,lookbehind:!0},{pattern:/(\[)(?!\d)\w+(?=\])/,lookbehind:!0}],function:[{pattern:/(\|\s*)@?(?!\d)\w+/,lookbehind:!0},/^\/?(?!\d)\w+/,/(?!\d)\w+(?=\()/],"attr-name":{pattern:/\w+\s*=\s*(?:(?!\d)\w+)?/,inside:{variable:{pattern:/(=\s*)(?!\d)\w+/,lookbehind:!0},operator:/=/}},punctuation:[/[\[\]().,:`]|->/],operator:[/[+\-*\/%]|==?=?|[!<>]=?|&&|\|\|?/,/\bis\s+(?:not\s+)?(?:div|even|odd)(?:\s+by)?\b/,/\b(?:eq|neq?|gt|lt|gt?e|lt?e|not|mod|or|and)\b/],keyword:/\b(?:false|off|on|no|true|yes)\b/},n.hooks.add("before-tokenize",function(e){var t=!1;n.languages["markup-templating"].buildPlaceholders(e,"smarty",/\{\*[\s\S]*?\*\}|\{[\s\S]+?\}/g,function(e){return"{/literal}"===e&&(t=!1),!t&&("{literal}"===e&&(t=!0),!0)})}),n.hooks.add("after-tokenize",function(e){n.languages["markup-templating"].tokenizePlaceholders(e,"smarty")})}(Prism); 49 | Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:_INSERT|COL)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURNS?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:TRUE|FALSE|NULL)\b/i,number:/\b0x[\da-f]+\b|\b\d+\.?\d*|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}; 50 | !function(n){var t={url:/url\((["']?).*?\1\)/i,string:{pattern:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,greedy:!0},interpolation:null,func:null,important:/\B!(?:important|optional)\b/i,keyword:{pattern:/(^|\s+)(?:(?:if|else|for|return|unless)(?=\s+|$)|@[\w-]+)/,lookbehind:!0},hexcode:/#[\da-f]{3,6}/i,number:/\b\d+(?:\.\d+)?%?/,boolean:/\b(?:true|false)\b/,operator:[/~|[+!\/%<>?=]=?|[-:]=|\*[*=]?|\.+|&&|\|\||\B-\B|\b(?:and|in|is(?: a| defined| not|nt)?|not|or)\b/],punctuation:/[{}()\[\];:,]/};t.interpolation={pattern:/\{[^\r\n}:]+\}/,alias:"variable",inside:{delimiter:{pattern:/^{|}$/,alias:"punctuation"},rest:t}},t.func={pattern:/[\w-]+\([^)]*\).*/,inside:{function:/^[^(]+/,rest:t}},n.languages.stylus={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},"atrule-declaration":{pattern:/(^\s*)@.+/m,lookbehind:!0,inside:{atrule:/^@[\w-]+/,rest:t}},"variable-declaration":{pattern:/(^[ \t]*)[\w$-]+\s*.?=[ \t]*(?:(?:\{[^}]*\}|.+)|$)/m,lookbehind:!0,inside:{variable:/^\S+/,rest:t}},statement:{pattern:/(^[ \t]*)(?:if|else|for|return|unless)[ \t]+.+/m,lookbehind:!0,inside:{keyword:/^\S+/,rest:t}},"property-declaration":{pattern:/((?:^|\{)([ \t]*))(?:[\w-]|\{[^}\r\n]+\})+(?:\s*:\s*|[ \t]+)[^{\r\n]*(?:;|[^{\r\n,](?=$)(?!(?:\r?\n|\r)(?:\{|\2[ \t]+)))/m,lookbehind:!0,inside:{property:{pattern:/^[^\s:]+/,inside:{interpolation:t.interpolation}},rest:t}},selector:{pattern:/(^[ \t]*)(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\))?|\{[^}\r\n]+\})+)(?:(?:\r?\n|\r)(?:\1(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\))?|\{[^}\r\n]+\})+)))*(?:,$|\{|(?=(?:\r?\n|\r)(?:\{|\1[ \t]+)))/m,lookbehind:!0,inside:{interpolation:t.interpolation,punctuation:/[{},]/}},func:t.func,string:t.string,interpolation:t.interpolation,punctuation:/[{}()\[\];:.]/}}(Prism); 51 | Prism.languages.swift=Prism.languages.extend("clike",{string:{pattern:/("|')(?:\\(?:\((?:[^()]|\([^)]+\))+\)|\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:{pattern:/\\\((?:[^()]|\([^)]+\))+\)/,inside:{delimiter:{pattern:/^\\\(|\)$/,alias:"variable"}}}}},keyword:/\b(?:as|associativity|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic(?:Type)?|else|enum|extension|fallthrough|final|for|func|get|guard|if|import|in|infix|init|inout|internal|is|lazy|left|let|mutating|new|none|nonmutating|operator|optional|override|postfix|precedence|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|Self|set|static|struct|subscript|super|switch|throws?|try|Type|typealias|unowned|unsafe|var|weak|where|while|willSet|__(?:COLUMN__|FILE__|FUNCTION__|LINE__))\b/,number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,constant:/\b(?:nil|[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,atrule:/@\b(?:IB(?:Outlet|Designable|Action|Inspectable)|class_protocol|exported|noreturn|NS(?:Copying|Managed)|objc|UIApplicationMain|auto_closure)\b/,builtin:/\b(?:[A-Z]\S+|abs|advance|alignof(?:Value)?|assert|contains|count(?:Elements)?|debugPrint(?:ln)?|distance|drop(?:First|Last)|dump|enumerate|equal|filter|find|first|getVaList|indices|isEmpty|join|last|lexicographicalCompare|map|max(?:Element)?|min(?:Element)?|numericCast|overlaps|partition|print(?:ln)?|reduce|reflect|reverse|sizeof(?:Value)?|sort(?:ed)?|split|startsWith|stride(?:of(?:Value)?)?|suffix|swap|toDebugString|toString|transcode|underestimateCount|unsafeBitCast|with(?:ExtendedLifetime|Unsafe(?:MutablePointers?|Pointers?)|VaList))\b/}),Prism.languages.swift.string.inside.interpolation.inside.rest=Prism.languages.swift; 52 | Prism.languages.typescript=Prism.languages.extend("javascript",{keyword:/\b(?:abstract|as|async|await|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|module|namespace|new|null|of|package|private|protected|public|readonly|return|require|set|static|super|switch|this|throw|try|type|typeof|undefined|var|void|while|with|yield)\b/,builtin:/\b(?:string|Function|any|number|boolean|Array|symbol|console|Promise|unknown|never)\b/}),Prism.languages.ts=Prism.languages.typescript; 53 | Prism.languages.vim={string:/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\r\n]|'')*'/,comment:/".*/,function:/\w+(?=\()/,keyword:/\b(?:ab|abbreviate|abc|abclear|abo|aboveleft|al|all|arga|argadd|argd|argdelete|argdo|arge|argedit|argg|argglobal|argl|arglocal|ar|args|argu|argument|as|ascii|bad|badd|ba|ball|bd|bdelete|be|bel|belowright|bf|bfirst|bl|blast|bm|bmodified|bn|bnext|bN|bNext|bo|botright|bp|bprevious|brea|break|breaka|breakadd|breakd|breakdel|breakl|breaklist|br|brewind|bro|browse|bufdo|b|buffer|buffers|bun|bunload|bw|bwipeout|ca|cabbrev|cabc|cabclear|caddb|caddbuffer|cad|caddexpr|caddf|caddfile|cal|call|cat|catch|cb|cbuffer|cc|ccl|cclose|cd|ce|center|cex|cexpr|cf|cfile|cfir|cfirst|cgetb|cgetbuffer|cgete|cgetexpr|cg|cgetfile|c|change|changes|chd|chdir|che|checkpath|checkt|checktime|cla|clast|cl|clist|clo|close|cmapc|cmapclear|cnew|cnewer|cn|cnext|cN|cNext|cnf|cnfile|cNfcNfile|cnorea|cnoreabbrev|col|colder|colo|colorscheme|comc|comclear|comp|compiler|conf|confirm|con|continue|cope|copen|co|copy|cpf|cpfile|cp|cprevious|cq|cquit|cr|crewind|cuna|cunabbrev|cu|cunmap|cw|cwindow|debugg|debuggreedy|delc|delcommand|d|delete|delf|delfunction|delm|delmarks|diffg|diffget|diffoff|diffpatch|diffpu|diffput|diffsplit|diffthis|diffu|diffupdate|dig|digraphs|di|display|dj|djump|dl|dlist|dr|drop|ds|dsearch|dsp|dsplit|earlier|echoe|echoerr|echom|echomsg|echon|e|edit|el|else|elsei|elseif|em|emenu|endfo|endfor|endf|endfunction|endfun|en|endif|endt|endtry|endw|endwhile|ene|enew|ex|exi|exit|exu|exusage|f|file|files|filetype|fina|finally|fin|find|fini|finish|fir|first|fix|fixdel|fo|fold|foldc|foldclose|folddoc|folddoclosed|foldd|folddoopen|foldo|foldopen|for|fu|fun|function|go|goto|gr|grep|grepa|grepadd|ha|hardcopy|h|help|helpf|helpfind|helpg|helpgrep|helpt|helptags|hid|hide|his|history|ia|iabbrev|iabc|iabclear|if|ij|ijump|il|ilist|imapc|imapclear|in|inorea|inoreabbrev|isearch|isp|isplit|iuna|iunabbrev|iu|iunmap|j|join|ju|jumps|k|keepalt|keepj|keepjumps|kee|keepmarks|laddb|laddbuffer|lad|laddexpr|laddf|laddfile|lan|language|la|last|later|lb|lbuffer|lc|lcd|lch|lchdir|lcl|lclose|let|left|lefta|leftabove|lex|lexpr|lf|lfile|lfir|lfirst|lgetb|lgetbuffer|lgete|lgetexpr|lg|lgetfile|lgr|lgrep|lgrepa|lgrepadd|lh|lhelpgrep|l|list|ll|lla|llast|lli|llist|lmak|lmake|lm|lmap|lmapc|lmapclear|lnew|lnewer|lne|lnext|lN|lNext|lnf|lnfile|lNf|lNfile|ln|lnoremap|lo|loadview|loc|lockmarks|lockv|lockvar|lol|lolder|lop|lopen|lpf|lpfile|lp|lprevious|lr|lrewind|ls|lt|ltag|lu|lunmap|lv|lvimgrep|lvimgrepa|lvimgrepadd|lw|lwindow|mak|make|ma|mark|marks|mat|match|menut|menutranslate|mk|mkexrc|mks|mksession|mksp|mkspell|mkvie|mkview|mkv|mkvimrc|mod|mode|m|move|mzf|mzfile|mz|mzscheme|nbkey|new|n|next|N|Next|nmapc|nmapclear|noh|nohlsearch|norea|noreabbrev|nu|number|nun|nunmap|omapc|omapclear|on|only|o|open|opt|options|ou|ounmap|pc|pclose|ped|pedit|pe|perl|perld|perldo|po|pop|popu|popup|pp|ppop|pre|preserve|prev|previous|p|print|P|Print|profd|profdel|prof|profile|promptf|promptfind|promptr|promptrepl|ps|psearch|pta|ptag|ptf|ptfirst|ptj|ptjump|ptl|ptlast|ptn|ptnext|ptN|ptNext|ptp|ptprevious|ptr|ptrewind|pts|ptselect|pu|put|pw|pwd|pyf|pyfile|py|python|qa|qall|q|quit|quita|quitall|r|read|rec|recover|redi|redir|red|redo|redr|redraw|redraws|redrawstatus|reg|registers|res|resize|ret|retab|retu|return|rew|rewind|ri|right|rightb|rightbelow|rub|ruby|rubyd|rubydo|rubyf|rubyfile|ru|runtime|rv|rviminfo|sal|sall|san|sandbox|sa|sargument|sav|saveas|sba|sball|sbf|sbfirst|sbl|sblast|sbm|sbmodified|sbn|sbnext|sbN|sbNext|sbp|sbprevious|sbr|sbrewind|sb|sbuffer|scripte|scriptencoding|scrip|scriptnames|se|set|setf|setfiletype|setg|setglobal|setl|setlocal|sf|sfind|sfir|sfirst|sh|shell|sign|sil|silent|sim|simalt|sla|slast|sl|sleep|sm|smagic|sm|smap|smapc|smapclear|sme|smenu|sn|snext|sN|sNext|sni|sniff|sno|snomagic|snor|snoremap|snoreme|snoremenu|sor|sort|so|source|spelld|spelldump|spe|spellgood|spelli|spellinfo|spellr|spellrepall|spellu|spellundo|spellw|spellwrong|sp|split|spr|sprevious|sre|srewind|sta|stag|startg|startgreplace|star|startinsert|startr|startreplace|stj|stjump|st|stop|stopi|stopinsert|sts|stselect|sun|sunhide|sunm|sunmap|sus|suspend|sv|sview|syncbind|t|tab|tabc|tabclose|tabd|tabdo|tabe|tabedit|tabf|tabfind|tabfir|tabfirst|tabl|tablast|tabm|tabmove|tabnew|tabn|tabnext|tabN|tabNext|tabo|tabonly|tabp|tabprevious|tabr|tabrewind|tabs|ta|tag|tags|tc|tcl|tcld|tcldo|tclf|tclfile|te|tearoff|tf|tfirst|th|throw|tj|tjump|tl|tlast|tm|tm|tmenu|tn|tnext|tN|tNext|to|topleft|tp|tprevious|tr|trewind|try|ts|tselect|tu|tu|tunmenu|una|unabbreviate|u|undo|undoj|undojoin|undol|undolist|unh|unhide|unlet|unlo|unlockvar|unm|unmap|up|update|verb|verbose|ve|version|vert|vertical|vie|view|vim|vimgrep|vimgrepa|vimgrepadd|vi|visual|viu|viusage|vmapc|vmapclear|vne|vnew|vs|vsplit|vu|vunmap|wa|wall|wh|while|winc|wincmd|windo|winp|winpos|win|winsize|wn|wnext|wN|wNext|wp|wprevious|wq|wqa|wqall|w|write|ws|wsverb|wv|wviminfo|X|xa|xall|x|xit|xm|xmap|xmapc|xmapclear|xme|xmenu|XMLent|XMLns|xn|xnoremap|xnoreme|xnoremenu|xu|xunmap|y|yank)\b/,builtin:/\b(?:autocmd|acd|ai|akm|aleph|allowrevins|altkeymap|ambiwidth|ambw|anti|antialias|arab|arabic|arabicshape|ari|arshape|autochdir|autoindent|autoread|autowrite|autowriteall|aw|awa|background|backspace|backup|backupcopy|backupdir|backupext|backupskip|balloondelay|ballooneval|balloonexpr|bdir|bdlay|beval|bex|bexpr|bg|bh|bin|binary|biosk|bioskey|bk|bkc|bomb|breakat|brk|browsedir|bs|bsdir|bsk|bt|bufhidden|buflisted|buftype|casemap|ccv|cdpath|cedit|cfu|ch|charconvert|ci|cin|cindent|cink|cinkeys|cino|cinoptions|cinw|cinwords|clipboard|cmdheight|cmdwinheight|cmp|cms|columns|com|comments|commentstring|compatible|complete|completefunc|completeopt|consk|conskey|copyindent|cot|cpo|cpoptions|cpt|cscopepathcomp|cscopeprg|cscopequickfix|cscopetag|cscopetagorder|cscopeverbose|cspc|csprg|csqf|cst|csto|csverb|cuc|cul|cursorcolumn|cursorline|cwh|debug|deco|def|define|delcombine|dex|dg|dict|dictionary|diff|diffexpr|diffopt|digraph|dip|dir|directory|dy|ea|ead|eadirection|eb|ed|edcompatible|ef|efm|ei|ek|enc|encoding|endofline|eol|ep|equalalways|equalprg|errorbells|errorfile|errorformat|esckeys|et|eventignore|expandtab|exrc|fcl|fcs|fdc|fde|fdi|fdl|fdls|fdm|fdn|fdo|fdt|fen|fenc|fencs|fex|ff|ffs|fileencoding|fileencodings|fileformat|fileformats|fillchars|fk|fkmap|flp|fml|fmr|foldcolumn|foldenable|foldexpr|foldignore|foldlevel|foldlevelstart|foldmarker|foldmethod|foldminlines|foldnestmax|foldtext|formatexpr|formatlistpat|formatoptions|formatprg|fp|fs|fsync|ft|gcr|gd|gdefault|gfm|gfn|gfs|gfw|ghr|gp|grepformat|grepprg|gtl|gtt|guicursor|guifont|guifontset|guifontwide|guiheadroom|guioptions|guipty|guitablabel|guitabtooltip|helpfile|helpheight|helplang|hf|hh|hi|hidden|highlight|hk|hkmap|hkmapp|hkp|hl|hlg|hls|hlsearch|ic|icon|iconstring|ignorecase|im|imactivatekey|imak|imc|imcmdline|imd|imdisable|imi|iminsert|ims|imsearch|inc|include|includeexpr|incsearch|inde|indentexpr|indentkeys|indk|inex|inf|infercase|insertmode|isf|isfname|isi|isident|isk|iskeyword|isprint|joinspaces|js|key|keymap|keymodel|keywordprg|km|kmp|kp|langmap|langmenu|laststatus|lazyredraw|lbr|lcs|linebreak|lines|linespace|lisp|lispwords|listchars|loadplugins|lpl|lsp|lz|macatsui|magic|makeef|makeprg|matchpairs|matchtime|maxcombine|maxfuncdepth|maxmapdepth|maxmem|maxmempattern|maxmemtot|mco|mef|menuitems|mfd|mh|mis|mkspellmem|ml|mls|mm|mmd|mmp|mmt|modeline|modelines|modifiable|modified|more|mouse|mousef|mousefocus|mousehide|mousem|mousemodel|mouses|mouseshape|mouset|mousetime|mp|mps|msm|mzq|mzquantum|nf|nrformats|numberwidth|nuw|odev|oft|ofu|omnifunc|opendevice|operatorfunc|opfunc|osfiletype|pa|para|paragraphs|paste|pastetoggle|patchexpr|patchmode|path|pdev|penc|pex|pexpr|pfn|ph|pheader|pi|pm|pmbcs|pmbfn|popt|preserveindent|previewheight|previewwindow|printdevice|printencoding|printexpr|printfont|printheader|printmbcharset|printmbfont|printoptions|prompt|pt|pumheight|pvh|pvw|qe|quoteescape|readonly|remap|report|restorescreen|revins|rightleft|rightleftcmd|rl|rlc|ro|rs|rtp|ruf|ruler|rulerformat|runtimepath|sbo|sc|scb|scr|scroll|scrollbind|scrolljump|scrolloff|scrollopt|scs|sect|sections|secure|sel|selection|selectmode|sessionoptions|sft|shcf|shellcmdflag|shellpipe|shellquote|shellredir|shellslash|shelltemp|shelltype|shellxquote|shiftround|shiftwidth|shm|shortmess|shortname|showbreak|showcmd|showfulltag|showmatch|showmode|showtabline|shq|si|sidescroll|sidescrolloff|siso|sj|slm|smartcase|smartindent|smarttab|smc|smd|softtabstop|sol|spc|spell|spellcapcheck|spellfile|spelllang|spellsuggest|spf|spl|splitbelow|splitright|sps|sr|srr|ss|ssl|ssop|stal|startofline|statusline|stl|stmp|su|sua|suffixes|suffixesadd|sw|swapfile|swapsync|swb|swf|switchbuf|sws|sxq|syn|synmaxcol|syntax|tabline|tabpagemax|tabstop|tagbsearch|taglength|tagrelative|tagstack|tal|tb|tbi|tbidi|tbis|tbs|tenc|term|termbidi|termencoding|terse|textauto|textmode|textwidth|tgst|thesaurus|tildeop|timeout|timeoutlen|title|titlelen|titleold|titlestring|toolbar|toolbariconsize|top|tpm|tsl|tsr|ttimeout|ttimeoutlen|ttm|tty|ttybuiltin|ttyfast|ttym|ttymouse|ttyscroll|ttytype|tw|tx|uc|ul|undolevels|updatecount|updatetime|ut|vb|vbs|vdir|verbosefile|vfile|viewdir|viewoptions|viminfo|virtualedit|visualbell|vop|wak|warn|wb|wc|wcm|wd|weirdinvert|wfh|wfw|whichwrap|wi|wig|wildchar|wildcharm|wildignore|wildmenu|wildmode|wildoptions|wim|winaltkeys|window|winfixheight|winfixwidth|winheight|winminheight|winminwidth|winwidth|wiv|wiw|wm|wmh|wmnu|wmw|wop|wrap|wrapmargin|wrapscan|writeany|writebackup|writedelay|ww|noacd|noai|noakm|noallowrevins|noaltkeymap|noanti|noantialias|noar|noarab|noarabic|noarabicshape|noari|noarshape|noautochdir|noautoindent|noautoread|noautowrite|noautowriteall|noaw|noawa|nobackup|noballooneval|nobeval|nobin|nobinary|nobiosk|nobioskey|nobk|nobl|nobomb|nobuflisted|nocf|noci|nocin|nocindent|nocompatible|noconfirm|noconsk|noconskey|nocopyindent|nocp|nocscopetag|nocscopeverbose|nocst|nocsverb|nocuc|nocul|nocursorcolumn|nocursorline|nodeco|nodelcombine|nodg|nodiff|nodigraph|nodisable|noea|noeb|noed|noedcompatible|noek|noendofline|noeol|noequalalways|noerrorbells|noesckeys|noet|noex|noexpandtab|noexrc|nofen|nofk|nofkmap|nofoldenable|nogd|nogdefault|noguipty|nohid|nohidden|nohk|nohkmap|nohkmapp|nohkp|nohls|noic|noicon|noignorecase|noim|noimc|noimcmdline|noimd|noincsearch|noinf|noinfercase|noinsertmode|nois|nojoinspaces|nojs|nolazyredraw|nolbr|nolinebreak|nolisp|nolist|noloadplugins|nolpl|nolz|noma|nomacatsui|nomagic|nomh|noml|nomod|nomodeline|nomodifiable|nomodified|nomore|nomousef|nomousefocus|nomousehide|nonu|nonumber|noodev|noopendevice|nopaste|nopi|nopreserveindent|nopreviewwindow|noprompt|nopvw|noreadonly|noremap|norestorescreen|norevins|nori|norightleft|norightleftcmd|norl|norlc|noro|nors|noru|noruler|nosb|nosc|noscb|noscrollbind|noscs|nosecure|nosft|noshellslash|noshelltemp|noshiftround|noshortname|noshowcmd|noshowfulltag|noshowmatch|noshowmode|nosi|nosm|nosmartcase|nosmartindent|nosmarttab|nosmd|nosn|nosol|nospell|nosplitbelow|nosplitright|nospr|nosr|nossl|nosta|nostartofline|nostmp|noswapfile|noswf|nota|notagbsearch|notagrelative|notagstack|notbi|notbidi|notbs|notermbidi|noterse|notextauto|notextmode|notf|notgst|notildeop|notimeout|notitle|noto|notop|notr|nottimeout|nottybuiltin|nottyfast|notx|novb|novisualbell|nowa|nowarn|nowb|noweirdinvert|nowfh|nowfw|nowildmenu|nowinfixheight|nowinfixwidth|nowiv|nowmnu|nowrap|nowrapscan|nowrite|nowriteany|nowritebackup|nows|invacd|invai|invakm|invallowrevins|invaltkeymap|invanti|invantialias|invar|invarab|invarabic|invarabicshape|invari|invarshape|invautochdir|invautoindent|invautoread|invautowrite|invautowriteall|invaw|invawa|invbackup|invballooneval|invbeval|invbin|invbinary|invbiosk|invbioskey|invbk|invbl|invbomb|invbuflisted|invcf|invci|invcin|invcindent|invcompatible|invconfirm|invconsk|invconskey|invcopyindent|invcp|invcscopetag|invcscopeverbose|invcst|invcsverb|invcuc|invcul|invcursorcolumn|invcursorline|invdeco|invdelcombine|invdg|invdiff|invdigraph|invdisable|invea|inveb|inved|invedcompatible|invek|invendofline|inveol|invequalalways|inverrorbells|invesckeys|invet|invex|invexpandtab|invexrc|invfen|invfk|invfkmap|invfoldenable|invgd|invgdefault|invguipty|invhid|invhidden|invhk|invhkmap|invhkmapp|invhkp|invhls|invhlsearch|invic|invicon|invignorecase|invim|invimc|invimcmdline|invimd|invincsearch|invinf|invinfercase|invinsertmode|invis|invjoinspaces|invjs|invlazyredraw|invlbr|invlinebreak|invlisp|invlist|invloadplugins|invlpl|invlz|invma|invmacatsui|invmagic|invmh|invml|invmod|invmodeline|invmodifiable|invmodified|invmore|invmousef|invmousefocus|invmousehide|invnu|invnumber|invodev|invopendevice|invpaste|invpi|invpreserveindent|invpreviewwindow|invprompt|invpvw|invreadonly|invremap|invrestorescreen|invrevins|invri|invrightleft|invrightleftcmd|invrl|invrlc|invro|invrs|invru|invruler|invsb|invsc|invscb|invscrollbind|invscs|invsecure|invsft|invshellslash|invshelltemp|invshiftround|invshortname|invshowcmd|invshowfulltag|invshowmatch|invshowmode|invsi|invsm|invsmartcase|invsmartindent|invsmarttab|invsmd|invsn|invsol|invspell|invsplitbelow|invsplitright|invspr|invsr|invssl|invsta|invstartofline|invstmp|invswapfile|invswf|invta|invtagbsearch|invtagrelative|invtagstack|invtbi|invtbidi|invtbs|invtermbidi|invterse|invtextauto|invtextmode|invtf|invtgst|invtildeop|invtimeout|invtitle|invto|invtop|invtr|invttimeout|invttybuiltin|invttyfast|invtx|invvb|invvisualbell|invwa|invwarn|invwb|invweirdinvert|invwfh|invwfw|invwildmenu|invwinfixheight|invwinfixwidth|invwiv|invwmnu|invwrap|invwrapscan|invwrite|invwriteany|invwritebackup|invws|t_AB|t_AF|t_al|t_AL|t_bc|t_cd|t_ce|t_Ce|t_cl|t_cm|t_Co|t_cs|t_Cs|t_CS|t_CV|t_da|t_db|t_dl|t_DL|t_EI|t_F1|t_F2|t_F3|t_F4|t_F5|t_F6|t_F7|t_F8|t_F9|t_fs|t_IE|t_IS|t_k1|t_K1|t_k2|t_k3|t_K3|t_k4|t_K4|t_k5|t_K5|t_k6|t_K6|t_k7|t_K7|t_k8|t_K8|t_k9|t_K9|t_KA|t_kb|t_kB|t_KB|t_KC|t_kd|t_kD|t_KD|t_ke|t_KE|t_KF|t_KG|t_kh|t_KH|t_kI|t_KI|t_KJ|t_KK|t_kl|t_KL|t_kN|t_kP|t_kr|t_ks|t_ku|t_le|t_mb|t_md|t_me|t_mr|t_ms|t_nd|t_op|t_RI|t_RV|t_Sb|t_se|t_Sf|t_SI|t_so|t_sr|t_te|t_ti|t_ts|t_ue|t_us|t_ut|t_vb|t_ve|t_vi|t_vs|t_WP|t_WS|t_xs|t_ZH|t_ZR)\b/,number:/\b(?:0x[\da-f]+|\d+(?:\.\d+)?)\b/i,operator:/\|\||&&|[-+.]=?|[=!](?:[=~][#?]?)?|[<>]=?[#?]?|[*\/%?]|\b(?:is(?:not)?)\b/,punctuation:/[{}[\](),;:]/}; 54 | Prism.languages.yaml={scalar:{pattern:/([\-:]\s*(?:![^\s]+)?[ \t]*[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)[^\r\n]+(?:\2[^\r\n]+)*)/,lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:/(\s*(?:^|[:\-,[{\r\n?])[ \t]*(?:![^\s]+)?[ \t]*)[^\r\n{[\]},#\s]+?(?=\s*:\s)/,lookbehind:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:/([:\-,[{]\s*(?:![^\s]+)?[ \t]*)(?:\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?)?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?)(?=[ \t]*(?:$|,|]|}))/m,lookbehind:!0,alias:"number"},boolean:{pattern:/([:\-,[{]\s*(?:![^\s]+)?[ \t]*)(?:true|false)[ \t]*(?=$|,|]|})/im,lookbehind:!0,alias:"important"},null:{pattern:/([:\-,[{]\s*(?:![^\s]+)?[ \t]*)(?:null|~)[ \t]*(?=$|,|]|})/im,lookbehind:!0,alias:"important"},string:{pattern:/([:\-,[{]\s*(?:![^\s]+)?[ \t]*)("|')(?:(?!\2)[^\\\r\n]|\\.)*\2(?=[ \t]*(?:$|,|]|}|\s*#))/m,lookbehind:!0,greedy:!0},number:{pattern:/([:\-,[{]\s*(?:![^\s]+)?[ \t]*)[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+\.?\d*|\.?\d+)(?:e[+-]?\d+)?|\.inf|\.nan)[ \t]*(?=$|,|]|})/im,lookbehind:!0},tag:/![^\s]+/,important:/[&*][\w]+/,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},Prism.languages.yml=Prism.languages.yaml; 55 | -------------------------------------------------------------------------------- /samples/complex.md: -------------------------------------------------------------------------------- 1 | # Recursion 2 | 3 | ## Base recursion 4 | 5 | Recursion in computer science is a method of solving a problem where the solution depends on solutions to smaller instances of the same problem. Such problems can generally be solved by iteration, but this needs to identify and index the smaller instances at programming time. 6 | 7 | ```js 8 | function recsum(x) { 9 | if (x === 1) { 10 | return x; 11 | } else { 12 | return x + recsum(x - 1); 13 | } 14 | } 15 | ``` 16 | Call stack: 17 | ```bash 18 | recsum(5) 19 | 5 + recsum(4) 20 | 5 + (4 + recsum(3)) 21 | 5 + (4 + (3 + recsum(2))) 22 | 5 + (4 + (3 + (2 + recsum(1)))) 23 | 5 + (4 + (3 + (2 + 1))) 24 | 15 25 | ``` 26 | 27 | [#recursion](./recursion.md) [#algorithms](./algorithms.md) 28 | 29 | ## Tail recursion 30 | 31 | In computer science, a tail call is a subroutine call performed as the final action of a procedure. If a tail call might lead to the same subroutine being called again later in the call chain, the subroutine is said to be tail-recursive, which is a special case of recursion. 32 | 33 | ```js 34 | function tailrecsum(x, running_total = 0) { 35 | if (x === 0) { 36 | return running_total; 37 | } else { 38 | return tailrecsum(x - 1, running_total + x); 39 | } 40 | } 41 | ``` 42 | Call stack: 43 | ```bash 44 | tailrecsum(5, 0) 45 | tailrecsum(4, 5) 46 | tailrecsum(3, 9) 47 | tailrecsum(2, 12) 48 | tailrecsum(1, 14) 49 | tailrecsum(0, 15) 50 | 15 51 | ``` 52 | 53 | Tail calls can be implemented without adding a new stack frame to the call stack. Most of the frame of the current procedure is no longer needed, and can be replaced by the frame of the tail call, modified as appropriate (similar to overlay for processes, but for function calls). The program can then jump to the called subroutine. Producing such code instead of a standard call sequence is called tail call elimination or tail call optimization. Tail call elimination allows procedure calls in tail position to be implemented as efficiently as goto statements 54 | 55 | [#recursion](./recursion.md) [#algorithms](./algorithms.md) 56 | 57 | ## Media stored in file system 58 | 59 | There is ability to write media files in 2 styles: inline and reference. 60 | 61 | % 62 | 63 | Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a browser. 64 | 65 | **Inline-style** 66 | 67 | With title: ![alt text](../samples/resources/nodejs.png "Node.js logo") 68 | 69 | Without title: ![alt text](../samples/resources/nodejs.png) 70 | 71 | **Reference-style** 72 | 73 | With title: ![alt text][node.js] 74 | 75 | Without title: ![alt text][node.js no title] 76 | 77 | [node.js]: ../samples/resources/nodejs.png "Recursion understanding" 78 | [node.js no title]: ../samples/resources/nodejs.png 79 | -------------------------------------------------------------------------------- /samples/media.md: -------------------------------------------------------------------------------- 1 | # Deck with media 2 | 3 | ## Media stored in file system 4 | 5 | Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a browser. 6 | 7 | **Inline-style** 8 | 9 | With title: ![alt text](../samples/resources/nodejs.png "Node.js logo") 10 | 11 | Without title: ![alt text](../samples/resources/nodejs.png) 12 | 13 | **Reference-style** 14 | 15 | With title: ![alt text][node.js] 16 | 17 | Without title: ![alt text][node.js no title] 18 | 19 | [node.js]: ../samples/resources/nodejs.png "Recursion understanding" 20 | [node.js no title]: ../samples/resources/nodejs.png 21 | 22 | ## Media stored remotely 23 | 24 | Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a browser. 25 | 26 | With title: ![alt text](https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/220px-Node.js_logo.svg.png "Node.js logo remotely") 27 | 28 | Without title: ![alt text](https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/220px-Node.js_logo.svg.png) 29 | -------------------------------------------------------------------------------- /samples/media_remote.md: -------------------------------------------------------------------------------- 1 | # Deck with media 2 | 3 | ## Media stored remotely 4 | 5 | Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside of a browser. 6 | 7 | With title: ![alt text](https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/220px-Node.js_logo.svg.png "Node.js logo remotely") 8 | 9 | Without title: ![alt text](https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/220px-Node.js_logo.svg.png) 10 | -------------------------------------------------------------------------------- /samples/resources/nodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashlinchak/mdanki/acbad247a1fcc02ac6a3ecbf37e4c0f93b1d8576/samples/resources/nodejs.png -------------------------------------------------------------------------------- /samples/resources/ruby_on_rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashlinchak/mdanki/acbad247a1fcc02ac6a3ecbf37e4c0f93b1d8576/samples/resources/ruby_on_rails.png -------------------------------------------------------------------------------- /samples/simple.md: -------------------------------------------------------------------------------- 1 | # Simple Card 2 | 3 | ## Card front 1 4 | 5 | Card back 1. All cards use markdown syntax. 6 | 7 | [#tree](tree.md) 8 | 9 | ## Card front 2 10 | 11 | Card back 2. 12 | 13 | ## B-tree complexity: access, insert, delete 14 | 15 | All: O(log n) 16 | 17 | [#complexity](complexity.md) [#tree](tree.md) [#tag]() 18 | 19 | ## Card with additional front data 20 | 21 | Front data goes here 22 | 23 | % 24 | 25 | Back data goes here 26 | 27 | And here 28 | 29 | [#tag1]() [#tag2]() 30 | 31 | ## Not allowed card 32 | -------------------------------------------------------------------------------- /src/configs/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const fs = require('fs'); 3 | const { argv } = require('yargs'); 4 | 5 | const settings = require('./settings'); 6 | 7 | const userSettings = () => { 8 | if (argv.config) { 9 | try { 10 | const data = fs.readFileSync(argv.config).toString(); 11 | return JSON.parse(data); 12 | } catch (error) { 13 | console.log(error); 14 | } 15 | } 16 | return {}; 17 | }; 18 | 19 | const configs = _.merge(settings, userSettings()); 20 | 21 | module.exports = configs; 22 | -------------------------------------------------------------------------------- /src/configs/settings.js: -------------------------------------------------------------------------------- 1 | const settings = { 2 | code: { 3 | defaultLanguage: 'bash', 4 | template : 'dark', // [default | dark] 5 | }, 6 | card: { 7 | separator : '(?=^##\\s)', 8 | frontBackSeparator: '%', 9 | tagPattern : '^\\[#(.*)\\]', 10 | }, 11 | deck: { 12 | titleSeparator: '^#\\s', 13 | defaultName : 'mdanki', 14 | }, 15 | template: { 16 | formats: { 17 | question: '{{Front}}', 18 | answer : '{{FrontSide}}\n\n
\n\n{{Back}}', 19 | css : '.card {\n font-family: Arial,"Helvetica Neue",Helvetica,sans-serif;\n font-size: 16px;\n color: black;\nbackground-color: white;\n}\ncode[class*="language-"],pre[class*="language-"] {\n font-size: 0.9em !important;\n}', 20 | }, 21 | }, 22 | }; 23 | 24 | module.exports = settings; 25 | -------------------------------------------------------------------------------- /src/file_serializer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | const fs = require('fs'); 3 | 4 | const configs = require('./configs'); 5 | const CardParser = require('./parsers/card_parser'); 6 | const MediaParser = require('./parsers/media_parser'); 7 | 8 | /** 9 | * @typedef {import('./models/card').Card} Card 10 | * @typedef {import('./models/media').Media} Media 11 | */ 12 | 13 | /** 14 | * Serialize file to cards, media and deck name 15 | * @typedef {Object} ParsedData 16 | * @property {string} deckName 17 | * @property {[Card]} cards 18 | * @property {[Media]} media 19 | */ 20 | 21 | class FileSerializer { 22 | /** 23 | * @param {string} source File path 24 | */ 25 | constructor(source) { 26 | this.source = source; 27 | } 28 | 29 | /** 30 | * @returns {ParsedData} 31 | */ 32 | async transform() { 33 | const mdString = fs.readFileSync(this.source).toString(); 34 | return this.splitByCards(mdString); 35 | } 36 | 37 | /** 38 | * @param {string} mdString Markdown string 39 | * @returns {ParsedData} 40 | * @private 41 | */ 42 | async splitByCards(mdString) { 43 | let rawCards = mdString 44 | .split(new RegExp(configs.card.separator, 'm')) 45 | .map((line) => line.trim()); 46 | 47 | const deckName = this.deckName(rawCards); 48 | 49 | // filter out deck title 50 | rawCards = rawCards.filter((str) => !str.startsWith(configs.deck.titleSeparator)); 51 | 52 | const dirtyCards = await Promise.all(rawCards.map((str) => CardParser.parse(str))); 53 | const cards = dirtyCards 54 | .filter((card) => card) 55 | // card should have front and back sides 56 | .filter((card) => card.front && card.back); 57 | 58 | // get media from markdown file 59 | const media = await this.mediaFromCards(cards); 60 | 61 | return { 62 | deckName, 63 | cards, 64 | media, 65 | }; 66 | } 67 | 68 | /** 69 | * @param {[string]} rawCards The array of strings which represent cards' data 70 | * @returns {string} 71 | * @private 72 | */ 73 | deckName(rawCards) { 74 | const deckName = rawCards 75 | .find((str) => str.match(new RegExp(configs.deck.titleSeparator))); 76 | 77 | if (!deckName) { return null; } 78 | 79 | return deckName.replace(/(#\s|\n)/g, ''); 80 | } 81 | 82 | /** 83 | * Search media in cards and add it to the media collection 84 | * @param {[Card]} cards 85 | * @returns {[Media]} 86 | * @private 87 | */ 88 | async mediaFromCards(cards) { 89 | const mediaParser = new MediaParser(this.source); 90 | 91 | for (const card of cards) { 92 | card.front = await mediaParser.parse(card.front); 93 | card.back = await mediaParser.parse(card.back); 94 | } 95 | 96 | return mediaParser.mediaList; 97 | } 98 | } 99 | 100 | module.exports = FileSerializer; 101 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path'); 3 | const Transformer = require('./transformer'); 4 | 5 | 6 | // eslint-disable-next-line no-unused-expressions 7 | require('yargs') 8 | .command( 9 | '$0 ', 10 | 'Convert Markdown file into anki\'s apkg file for importing.', 11 | () => {}, 12 | async (argv) => { 13 | const transformer = new Transformer( 14 | path.resolve(argv.source), 15 | path.resolve(argv.target), 16 | ); 17 | await transformer.transform(); 18 | }, 19 | ) 20 | .example('$0 study.md anki.apk --deck Study') 21 | .option('config', { 22 | type : 'string', 23 | description: 'Configuration file location', 24 | }) 25 | .option('deck', { 26 | type : 'string', 27 | description: 'Deck name', 28 | }) 29 | .argv; 30 | -------------------------------------------------------------------------------- /src/models/card.js: -------------------------------------------------------------------------------- 1 | const { sanitizeString } = require('../utils'); 2 | 3 | /** 4 | * @typedef {Object} Card 5 | * @property {string} front 6 | * @property {string} back 7 | * @property {[string]} [tags=[]] 8 | */ 9 | 10 | class Card { 11 | /** 12 | * @param {string} front 13 | * @param {string} back 14 | * @param {[string]} tags 15 | */ 16 | constructor(front, back, tags = []) { 17 | this.front = front; 18 | this.back = back; 19 | this.tags = tags; 20 | } 21 | 22 | /** 23 | * Add tag to card in supported format 24 | * @param {string} dirtyTag 25 | * @returns {void} 26 | */ 27 | addTag(dirtyTag) { 28 | const tag = sanitizeString(dirtyTag); 29 | if (tag) { 30 | this.tags.push(tag); 31 | } 32 | } 33 | } 34 | 35 | module.exports = Card; 36 | -------------------------------------------------------------------------------- /src/models/deck.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { default: AnkiExport } = require('anki-apkg-export'); 3 | 4 | const Template = require('./template'); 5 | 6 | /** 7 | * @typedef {import('./template').Template} Template 8 | * @typedef {import('./card').Card} Card 9 | * @typedef {import('./media').Media} Media 10 | * @typedef {import('anki-apkg-export').default} AnkiExport 11 | */ 12 | 13 | /** 14 | * @typedef {Object} Deck 15 | * @property {string} name 16 | * @property {Template} template 17 | * @property {[Card]} cards 18 | * @property {Object} [options] 19 | */ 20 | 21 | class Deck { 22 | /** 23 | * @param {string} name 24 | * @param {Template} template 25 | * @param {Object} [options={}] 26 | */ 27 | constructor(name, options = {}) { 28 | this.name = name; 29 | this.options = options; 30 | this.cards = []; 31 | this.mediaCollection = []; 32 | this.template = new Template(); 33 | this.ankiExport = new AnkiExport(this.name, this.template); 34 | } 35 | 36 | /** 37 | * @param {Card} card 38 | * @returns {void} 39 | */ 40 | addCard(card) { 41 | this.cards.push(card); 42 | } 43 | 44 | /** 45 | * @param {Media} media 46 | * @returns {void} 47 | */ 48 | addMedia(media) { 49 | this.mediaCollection.push(media); 50 | } 51 | 52 | /** 53 | * Save deck in a file which is available for importing to Anki 54 | * @param {string} target File path 55 | * @returns {void} 56 | */ 57 | async save(target) { 58 | this.addDataToAnkiExporter(); 59 | await this.export(target); 60 | } 61 | 62 | /** 63 | * Add cards and media to Anki exporter 64 | * @returns {void} 65 | * @private 66 | */ 67 | addDataToAnkiExporter() { 68 | // cards 69 | this.cards.forEach((card) => { 70 | const { front, back, tags } = card; 71 | this.ankiExport.addCard(front, back, { tags }); 72 | }); 73 | 74 | // media 75 | this.mediaCollection.forEach((media) => { 76 | this.ankiExport.addMedia(media.fileName, media.data); 77 | }); 78 | } 79 | 80 | /** 81 | * @param {string} target File path 82 | * @private 83 | */ 84 | async export(target) { 85 | try { 86 | const zip = await this.ankiExport.save(); 87 | fs.writeFileSync(target, zip, 'binary'); 88 | console.log(`The deck "${this.name}" has been generated in ${target}`); 89 | } catch (error) { 90 | console.log(error); 91 | } 92 | } 93 | } 94 | 95 | module.exports = Deck; 96 | -------------------------------------------------------------------------------- /src/models/media.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | /** 4 | * @typedef {Object} Media 5 | * @property {string} fileName 6 | * @property {any} data 7 | */ 8 | 9 | class Media { 10 | /** 11 | * @param {any} data 12 | * @param {string} [fileName] Checksum is the default value if not other specified 13 | */ 14 | constructor(data, fileName) { 15 | this.data = data; 16 | this.fileName = fileName; 17 | } 18 | 19 | /** 20 | * @returns {string} File data digest 21 | */ 22 | get checksum() { 23 | return crypto 24 | .createHash('md5') 25 | .update(this.data, 'utf8') 26 | .digest('hex'); 27 | } 28 | } 29 | 30 | module.exports = Media; 31 | -------------------------------------------------------------------------------- /src/models/template.js: -------------------------------------------------------------------------------- 1 | const { 2 | question: defaultQuestion, 3 | answer: defaultAnswer, 4 | css: defaultCss, 5 | } = require('../configs').template.formats; 6 | 7 | /** 8 | * @typedef {Object} Template 9 | * @property {string} questionFormat 10 | * @property {string} answerFormat 11 | * @property {string} css 12 | */ 13 | 14 | class Template { 15 | /** 16 | * @param {string} questionFormat 17 | * @param {string} answerFormat 18 | * @param {string} css 19 | */ 20 | constructor(questionFormat, answerFormat, css) { 21 | this.questionFormat = questionFormat || defaultQuestion; 22 | this.answerFormat = answerFormat || defaultAnswer; 23 | this.css = css || defaultCss; 24 | } 25 | } 26 | 27 | module.exports = Template; 28 | -------------------------------------------------------------------------------- /src/parsers/base_parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base parser which should be inherited 3 | * @typedef {Object} BaseParser 4 | */ 5 | 6 | class BaseParser { 7 | constructor(options = {}) { 8 | this.options = options; 9 | } 10 | 11 | /** 12 | * Create a new instance of parser and run parse() method 13 | * parse() method should be implemented in the inherited class 14 | * @param {string} data 15 | * @param {Object} options 16 | */ 17 | static parse(data, options = {}) { 18 | return new this(options).parse(data); 19 | } 20 | } 21 | 22 | module.exports = BaseParser; 23 | -------------------------------------------------------------------------------- /src/parsers/card_parser.js: -------------------------------------------------------------------------------- 1 | const BaseParser = require('./base_parser'); 2 | const MdParser = require('./md_parser'); 3 | const Card = require('../models/card'); 4 | const configs = require('../configs'); 5 | const { trimArray } = require('../utils'); 6 | 7 | /** 8 | * @typedef {import('../models/card').Card} Card 9 | * @typedef {import('./base_parser').BaseParser} BaseParser 10 | * / 11 | 12 | /** 13 | * Parse a string to Card model 14 | * @typedef {Object} ParsedCardLine 15 | * @implements {BaseParser} 16 | * @property {[string]} front 17 | * @property {[string]} back 18 | * @property {[string]} tags 19 | */ 20 | 21 | class CardParser extends BaseParser { 22 | constructor({ convertToHtml = true } = {}) { 23 | super({ convertToHtml }); 24 | this.splitRe = new RegExp(`^${configs.card.frontBackSeparator}$`, 'm'); 25 | this.tagRe = new RegExp(configs.card.tagPattern); 26 | } 27 | 28 | /** 29 | * Parse a string to Card model 30 | * @param {string} string Card in string 31 | * @returns {Promise} 32 | */ 33 | async parse(string = '') { 34 | const cardLines = string 35 | .split(this.splitRe) 36 | .map((item) => item.split('\n')) 37 | .map((arr) => arr.map((str) => str.trimEnd())); 38 | 39 | // not allowed cards with only front side 40 | if (cardLines.length === 1 && !cardLines[0].filter((line) => line).length) { 41 | return null; 42 | } 43 | const { front, back, tags } = this.parseCardLines(cardLines); 44 | 45 | if (!this.options.convertToHtml) { 46 | return new Card(front.join(), back.join(), tags); 47 | } 48 | const frontHtml = await this.linesToHtml(front); 49 | const backHtml = await this.linesToHtml(back); 50 | 51 | return new Card(frontHtml, backHtml, tags); 52 | } 53 | 54 | /** 55 | * @param {[string]} cardLines 56 | * @returns {ParsedCardLine} 57 | * @private 58 | */ 59 | parseCardLines(cardLines) { 60 | const front = []; 61 | const back = []; 62 | const tags = []; 63 | 64 | const fillBackAndTags = (line) => { 65 | // set tags 66 | if (this.tagRe.test(line)) { 67 | tags.push(...this.parseTags(line)); 68 | return; 69 | } 70 | 71 | // set back 72 | // skip first blank lines 73 | if (back.length === 0 && !line) { return; } 74 | 75 | back.push(line); 76 | }; 77 | 78 | if (cardLines.length === 1) { 79 | trimArray(cardLines[0]) 80 | .forEach((line) => { 81 | // we should set front first 82 | if (front.length === 0) { 83 | front.push(line); 84 | return; 85 | } 86 | 87 | fillBackAndTags(line); 88 | }); 89 | } else { 90 | // front card has multiple lines 91 | front.push(...cardLines[0]); 92 | 93 | trimArray(cardLines[1]) 94 | .forEach((line) => fillBackAndTags(line)); 95 | } 96 | 97 | return { 98 | front: trimArray(front), 99 | back : trimArray(back), 100 | tags : trimArray(tags), 101 | }; 102 | } 103 | 104 | /** 105 | * @param {string} line 106 | * @returns {[string]} 107 | * @private 108 | */ 109 | parseTags(line) { 110 | const data = line.split(' ') 111 | .map((str) => str.trim()) 112 | .map((str) => { 113 | const parts = this.tagRe.exec(str); 114 | return parts[1]; 115 | }) 116 | .filter((str) => str); 117 | 118 | return data; 119 | } 120 | 121 | /** 122 | * Convert card lines to html 123 | * @param {[string]} lines 124 | * @returns {Promise} 125 | * @private 126 | */ 127 | async linesToHtml(lines) { 128 | const string = lines.join('\n'); 129 | 130 | return MdParser.parse(string); 131 | } 132 | } 133 | 134 | 135 | module.exports = CardParser; 136 | -------------------------------------------------------------------------------- /src/parsers/md_parser.js: -------------------------------------------------------------------------------- 1 | const marked = require('marked'); 2 | const Prism = require('prismjs'); 3 | 4 | const BaseParser = require('./base_parser'); 5 | const configs = require('../configs'); 6 | 7 | // languages 8 | require('prismjs/components/prism-actionscript'); 9 | require('prismjs/components/prism-applescript'); 10 | require('prismjs/components/prism-aspnet'); 11 | require('prismjs/components/prism-bash'); 12 | require('prismjs/components/prism-basic'); 13 | require('prismjs/components/prism-batch'); 14 | require('prismjs/components/prism-c'); 15 | require('prismjs/components/prism-coffeescript'); 16 | require('prismjs/components/prism-cpp'); 17 | require('prismjs/components/prism-csharp'); 18 | require('prismjs/components/prism-d'); 19 | require('prismjs/components/prism-dart'); 20 | require('prismjs/components/prism-erlang'); 21 | require('prismjs/components/prism-fsharp'); 22 | require('prismjs/components/prism-go'); 23 | require('prismjs/components/prism-graphql'); 24 | require('prismjs/components/prism-groovy'); 25 | require('prismjs/components/prism-handlebars'); 26 | require('prismjs/components/prism-java'); 27 | require('prismjs/components/prism-json'); 28 | require('prismjs/components/prism-latex'); 29 | require('prismjs/components/prism-less'); 30 | require('prismjs/components/prism-livescript'); 31 | require('prismjs/components/prism-lua'); 32 | require('prismjs/components/prism-makefile'); 33 | require('prismjs/components/prism-markdown'); 34 | require('prismjs/components/prism-markup-templating'); 35 | require('prismjs/components/prism-nginx'); 36 | require('prismjs/components/prism-objectivec'); 37 | require('prismjs/components/prism-pascal'); 38 | require('prismjs/components/prism-perl'); 39 | require('prismjs/components/prism-php'); 40 | require('prismjs/components/prism-powershell'); 41 | require('prismjs/components/prism-python'); 42 | require('prismjs/components/prism-r'); 43 | require('prismjs/components/prism-ruby'); 44 | require('prismjs/components/prism-rust'); 45 | require('prismjs/components/prism-sass'); 46 | require('prismjs/components/prism-scheme'); 47 | require('prismjs/components/prism-smalltalk'); 48 | require('prismjs/components/prism-smarty'); 49 | require('prismjs/components/prism-sql'); 50 | require('prismjs/components/prism-stylus'); 51 | require('prismjs/components/prism-swift'); 52 | require('prismjs/components/prism-typescript'); 53 | require('prismjs/components/prism-vim'); 54 | require('prismjs/components/prism-yaml.min'); 55 | 56 | 57 | // aliases 58 | Prism.languages['c#'] = Prism.languages.csharp; 59 | Prism.languages['f#'] = Prism.languages.fsharp; 60 | Prism.languages.sh = Prism.languages.bash; 61 | Prism.languages.md = Prism.languages.markdown; 62 | Prism.languages.py = Prism.languages.python; 63 | Prism.languages.yml = Prism.languages.yaml; 64 | Prism.languages.rb = Prism.languages.ruby; 65 | 66 | /** 67 | * @typedef {import('marked').Renderer} MarkedRenderer 68 | * @typedef {import('./base_parser').BaseParser} BaseParser 69 | */ 70 | 71 | /** 72 | * Parse a markdown string to HTML 73 | * @typedef {Object} MdParser 74 | * @implements {BaseParser} 75 | * @property {MarkedRenderer} renderer 76 | */ 77 | 78 | class MdParser extends BaseParser { 79 | constructor(options) { 80 | super(options); 81 | this.initMarked(); 82 | this.renderer = new marked.Renderer(); 83 | } 84 | 85 | /** 86 | * Highlight a code with prism.js 87 | * @param {string} code 88 | * @param {string} lang 89 | */ 90 | highlight(code, lang) { 91 | const parsedLang = lang || configs.code.defaultLanguage; 92 | if (Prism.languages[parsedLang]) { 93 | return Prism.highlight(code, Prism.languages[parsedLang], parsedLang); 94 | } 95 | return code; 96 | } 97 | 98 | /** 99 | * Init marked with Prismjs 100 | * @returns {void} 101 | * @private 102 | */ 103 | initMarked() { 104 | marked.setOptions({ 105 | renderer : this.renderer, 106 | gfm : true, 107 | tables : true, 108 | breaks : true, 109 | pedantic : false, 110 | sanitize : false, 111 | smartLists : true, 112 | smartypants: false, 113 | mangle : false, 114 | highlight : this.highlight, 115 | }); 116 | } 117 | 118 | /** 119 | * 120 | * @param {string} mdString Markdown string 121 | * @returns {Promise} 122 | */ 123 | async parse(mdString) { 124 | return new Promise((resolve, reject) => { 125 | marked.parse(mdString, (err, result) => { 126 | if (err) { 127 | return reject(err); 128 | } 129 | 130 | return resolve(result); 131 | }); 132 | }); 133 | } 134 | } 135 | 136 | module.exports = MdParser; 137 | -------------------------------------------------------------------------------- /src/parsers/media_parser.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const axios = require('axios'); 3 | const fs = require('fs'); 4 | 5 | const BaseParser = require('./base_parser'); 6 | const Media = require('../models/media'); 7 | const { 8 | getExtensionFromUrl, 9 | replaceAsync, 10 | } = require('../utils'); 11 | 12 | /** 13 | * @typedef {import('../models/media').Media} Media 14 | * @typedef {import('./base_parser').BaseParser} BaseParser 15 | */ 16 | 17 | /** 18 | * @typedef {Object} MediaParser 19 | * @property {string} source 20 | * @property {[Media]} mediaList=[] 21 | */ 22 | 23 | class MediaParser extends BaseParser { 24 | constructor(source, options = {}) { 25 | super(options); 26 | this.source = source; 27 | this.mediaList = []; 28 | this.srcRe = new RegExp('src="([^"]*?)"', 'g'); 29 | } 30 | 31 | /** 32 | * Prepare media from card's side 33 | * @param {string} side 34 | */ 35 | parse(side) { 36 | return replaceAsync(side, this.srcRe, this.replacer.bind(this)); 37 | } 38 | 39 | async replacer(match, p1) { 40 | let data; 41 | let fileExt; 42 | 43 | if (p1.startsWith('http')) { 44 | const resp = await axios.get(p1, { 45 | responseType: 'arraybuffer', 46 | }); 47 | data = resp.data; 48 | fileExt = getExtensionFromUrl(p1); 49 | } else { 50 | const filePath = path.resolve(path.dirname(this.source), p1); 51 | fileExt = path.extname(filePath); 52 | data = fs.readFileSync(filePath); 53 | } 54 | 55 | const media = new Media(data); 56 | media.fileName = `${media.checksum}${fileExt}`; 57 | 58 | this.addMedia(media); 59 | 60 | return `src="${media.fileName}"`; 61 | } 62 | 63 | addMedia(media) { 64 | const hasMedia = this.mediaList.some((item) => item.checksum === media.checksum); 65 | if (hasMedia) { return; } 66 | 67 | this.mediaList.push(media); 68 | } 69 | } 70 | 71 | module.exports = MediaParser; 72 | -------------------------------------------------------------------------------- /src/transformer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { argv } = require('yargs'); 4 | const glob = require('glob'); 5 | const Promise = require('bluebird'); 6 | 7 | const FileSerializer = require('./file_serializer'); 8 | const configs = require('./configs'); 9 | const Deck = require('./models/deck'); 10 | const Media = require('./models/media'); 11 | 12 | const AVAILABLE_FILE_EXTENSIONS = ['.md', '.markdown']; 13 | 14 | /** 15 | * @typedef {import('./models/deck').Deck} Deck 16 | * @typedef {import('./models/card').Card} Card 17 | * @typedef {import('./models/media').Media} Media 18 | */ 19 | 20 | /** 21 | * Create anki cards from markdown files 22 | * @typedef {Object} Transformer 23 | * @property {string} sourcePath Path to markdown file(s) 24 | * @property {string} targetPath Path for storing .apkg file 25 | * @property {Deck} deck 26 | */ 27 | 28 | class Transformer { 29 | /** 30 | * @param {string} sourcePath Path to markdown file(s) 31 | * @param {string} targetPath Path for storing .apkg file 32 | */ 33 | constructor(sourcePath, targetPath) { 34 | this.sourcePath = sourcePath; 35 | this.targetPath = targetPath; 36 | this.deck = null; 37 | } 38 | 39 | /** 40 | * Transform markdown files to .apkg file 41 | * @returns {Promise} 42 | */ 43 | async transform() { 44 | this.validate(); 45 | await this.transformToDeck(); 46 | } 47 | 48 | /** 49 | * Transform markdown to deck 50 | * @returns {Promise} 51 | * @private 52 | */ 53 | async transformToDeck() { 54 | let deckName; 55 | const cards = []; 56 | const media = []; 57 | 58 | if (fs.lstatSync(this.sourcePath).isDirectory()) { 59 | const allowedExtStr = AVAILABLE_FILE_EXTENSIONS.map((ex) => ex.replace('.', '')).join(','); 60 | const files = glob.sync(`${this.sourcePath}/**/*.{${allowedExtStr}}`); 61 | 62 | await Promise.each(files, async (file) => { 63 | const fileSerializer = new FileSerializer(file); 64 | 65 | const { 66 | cards : fileCards, 67 | media : fileMedia, 68 | } = await fileSerializer.transform(); 69 | cards.push(...fileCards); 70 | media.push(...fileMedia); 71 | }); 72 | } else { 73 | const fileSerializer = new FileSerializer(this.sourcePath); 74 | const { 75 | deckName: fileDeckName, 76 | cards : fileCards, 77 | media : fileMedia, 78 | } = await fileSerializer.transform(); 79 | deckName = fileDeckName; 80 | cards.push(...fileCards); 81 | media.push(...fileMedia); 82 | } 83 | 84 | if (!cards.length) { 85 | console.log('No cards found. Check you markdown file(s)'); 86 | process.exit(1); 87 | } 88 | 89 | this.deck = new Deck(this.calculateDeckName(deckName)); 90 | 91 | await this.exportCards(cards, media); 92 | } 93 | 94 | /** 95 | * @param {string} generatedName 96 | * @returns {string} Default deck name 97 | * @private 98 | */ 99 | calculateDeckName(generatedName = null) { 100 | return argv.deck || generatedName || configs.deck.defaultName; 101 | } 102 | 103 | /** 104 | * @param {[Card]} cards 105 | * @param {[Media]} media 106 | * @returns {void} 107 | * @private 108 | */ 109 | async exportCards(cards, media) { 110 | this.addResourcesToDeck(); 111 | this.addCardsToDeck(cards); 112 | this.addMediaItemsToDeck(media); 113 | 114 | await this.deck.save(this.targetPath); 115 | } 116 | 117 | /** 118 | * Adds required resources to deck 119 | * @returns {void} 120 | * @private 121 | */ 122 | addResourcesToDeck() { 123 | // add media for code highlighting 124 | this.deck.addMedia(this.toMedia('_highlight.js', path.resolve(__dirname, '../resources/highlight.js'))); 125 | this.deck.addMedia(this.toMedia('_prism.js', path.resolve(__dirname, '../resources/prism.js'))); 126 | 127 | if (configs.code.template === 'dark') { 128 | this.deck.addMedia(this.toMedia('_highlight_dark.css', path.resolve(__dirname, '../resources/dark.css'))); 129 | } else { 130 | this.deck.addMedia(this.toMedia('_highlight_default.css', path.resolve(__dirname, '../resources/default.css'))); 131 | } 132 | } 133 | 134 | /** 135 | * @param {[Card]} cards 136 | * @returns {void} 137 | * @private 138 | */ 139 | addCardsToDeck(cards) { 140 | cards.forEach((card) => this.deck.addCard(card)); 141 | } 142 | 143 | /** 144 | * @param {[Media]} items 145 | * @returns {void} 146 | * @private 147 | */ 148 | addMediaItemsToDeck(items) { 149 | items.forEach((item) => this.deck.addMedia(item)); 150 | } 151 | 152 | /** 153 | * @param {string} fileName 154 | * @param {string} filePath 155 | * @returns {Media} 156 | * @private 157 | */ 158 | toMedia(fileName, filePath) { 159 | const data = fs.readFileSync(filePath); 160 | return new Media(data, fileName); 161 | } 162 | 163 | /** 164 | * @returns {void} 165 | * @private 166 | */ 167 | validate() { 168 | this.validatePath(this.sourcePath); 169 | this.validateExt(this.sourcePath); 170 | } 171 | 172 | /** 173 | * @param {string} checkPath 174 | * @returns {void|process.exit(1)} 175 | * @private 176 | */ 177 | validatePath(checkPath) { 178 | if (!fs.existsSync(checkPath)) { 179 | console.log(`${checkPath} does not exists`); 180 | process.exit(1); 181 | } 182 | } 183 | 184 | /** 185 | * @param {string} filePath 186 | * @returns {void|process.exit(1)} 187 | * @private 188 | */ 189 | validateExt(filePath) { 190 | const ext = path.extname(filePath); 191 | 192 | if (ext && !AVAILABLE_FILE_EXTENSIONS.includes(ext)) { 193 | console.log(`${filePath} has not allowed extension`); 194 | process.exit(1); 195 | } 196 | } 197 | } 198 | 199 | 200 | module.exports = Transformer; 201 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Trim string and replaces spaces with underscore 3 | * Used for making tags 4 | * @param {string} str 5 | * @returns {string} 6 | */ 7 | const sanitizeString = (str) => str 8 | .trim() 9 | .replace(/\s/g, '_'); 10 | 11 | /** 12 | * Trim array from end 13 | * @param {[string]} array 14 | * @returns {[string]} 15 | */ 16 | const trimArrayEnd = (array) => { 17 | const trimmedArray = []; 18 | let added = false; 19 | 20 | for (let i = array.length - 1; i >= 0; i -= 1) { 21 | if (array[i] || added) { 22 | trimmedArray.unshift(array[i]); 23 | added = true; 24 | } 25 | } 26 | 27 | return trimmedArray; 28 | }; 29 | 30 | /** 31 | * Trim array from start 32 | * @param {[string]} array 33 | * @returns {[string]} 34 | */ 35 | const trimArrayStart = (array) => { 36 | const trimmedArray = []; 37 | let added = false; 38 | 39 | for (let i = 0; i < array.length; i += 1) { 40 | if (array[i] || added) { 41 | trimmedArray.push(array[i]); 42 | added = true; 43 | } 44 | } 45 | 46 | return trimmedArray; 47 | }; 48 | 49 | /** 50 | * Trim array 51 | * @param {[string]} array 52 | * @returns {[string]} 53 | */ 54 | const trimArray = (array) => trimArrayEnd( 55 | trimArrayStart(array), 56 | ); 57 | 58 | /** 59 | * Get extension from URL 60 | * @param {[string]} url 61 | * @returns {[string]} 62 | */ 63 | const getExtensionFromUrl = (url) => { 64 | const extension = url 65 | .split(/[#?]/)[0] 66 | .split('.') 67 | .pop() 68 | .trim(); 69 | 70 | return `.${extension}`; 71 | }; 72 | 73 | async function replaceAsync(str, regex, asyncFn) { 74 | const tasks = []; 75 | 76 | // fill replacers with fake call 77 | str.replace(regex, (match, ...args) => { 78 | const promise = asyncFn(match, ...args); 79 | tasks.push(promise); 80 | }); 81 | 82 | const data = await Promise.all(tasks); 83 | 84 | return str.replace(regex, () => data.shift()); 85 | } 86 | 87 | module.exports = { 88 | sanitizeString, 89 | trimArrayStart, 90 | trimArrayEnd, 91 | trimArray, 92 | getExtensionFromUrl, 93 | replaceAsync, 94 | }; 95 | --------------------------------------------------------------------------------