diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index fe48d755..43cbfd70 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -st3v3n.mw@gmail.com. +mail@stephenmwangi.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/src/algorithms/base/srs-algorithm.ts b/src/algorithms/base/srs-algorithm.ts index 8054d5d9..4e375ae6 100644 --- a/src/algorithms/base/srs-algorithm.ts +++ b/src/algorithms/base/srs-algorithm.ts @@ -5,7 +5,7 @@ export class SrsAlgorithm { public static getInstance(): ISrsAlgorithm { if (!SrsAlgorithm.instance) { - throw Error("there is no SrsAlgorithm instance."); + throw new Error("there is no SrsAlgorithm instance."); } return SrsAlgorithm.instance; } diff --git a/src/constants.ts b/src/constants.ts index 76f4436e..136d3794 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,7 +3,6 @@ export const SCHEDULING_INFO_REGEX = /^---\r?\n((?:.*\r?\n)*)sr-due: (.+)\r?\nsr-interval: (\d+)\r?\nsr-ease: (\d+)\r?\n((?:.*\r?\n)?)---/; export const YAML_FRONT_MATTER_REGEX = /^---\r?\n((?:.*\r?\n)*?)---/; -export const NON_LETTER_SYMBOLS_REGEX = /[!-/:-@[-`{-~}\s]/g; export const MULTI_SCHEDULING_EXTRACTOR = /!([\d-]+),(\d+),(\d+)/gm; export const LEGACY_SCHEDULING_EXTRACTOR = //gm; diff --git a/src/data-store-algorithm/data-store-algorithm.ts b/src/data-store-algorithm/data-store-algorithm.ts index 091129b5..339c7472 100644 --- a/src/data-store-algorithm/data-store-algorithm.ts +++ b/src/data-store-algorithm/data-store-algorithm.ts @@ -5,7 +5,7 @@ export class DataStoreAlgorithm { public static getInstance(): IDataStoreAlgorithm { if (!DataStoreAlgorithm.instance) { - throw Error("there is no DataStoreAlgorithm instance."); + throw new Error("there is no DataStoreAlgorithm instance."); } return DataStoreAlgorithm.instance; } diff --git a/src/data-stores/base/data-store.ts b/src/data-stores/base/data-store.ts index ef9750fa..9fb5c7c5 100644 --- a/src/data-stores/base/data-store.ts +++ b/src/data-stores/base/data-store.ts @@ -17,7 +17,7 @@ export class DataStore { public static getInstance(): IDataStore { if (!DataStore.instance) { - throw Error("there is no DataStore instance."); + throw new Error("there is no DataStore instance."); } return DataStore.instance; } diff --git a/src/parser.ts b/src/parser.ts index fe56bc23..5423ab0e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -33,6 +33,20 @@ function areParserOptionsEqual(options1: ParserOptions, options2: ParserOptions) export function generateParser(options: ParserOptions): Parser { let grammar: string | null = null; + // Debug the grammar before generating the parser `generate(grammar)` from the grammar. + if (debugParser) { + if (grammar === null) { + grammar = generateGrammar(options); + } + console.log( + "The parsers grammar is provided below. You can test it with https://peggyjs.org/online.html.", + ); + console.log({ + info: "Copy the grammar by right-clicking on the property grammar and copying it as a string. Then, paste it in https://peggyjs.org/online.html.", + grammar: grammar, + }); + } + // If the parser did not already exist or if the parser options changed since last the last // parser was generated, we generate a new parser. Otherwise, we skip the block to save // some execution time. @@ -53,56 +67,111 @@ export function generateParser(options: ParserOptions): Parser { } } else { if (debugParser) { - console.log("Parser already existed. No need to generate a new parser."); - } - } - - if (debugParser) { - if (grammar === null) { - grammar = generateGrammar(options); + console.log("Parser already exists. No need to generate a new parser."); } - console.log( - "The parsers grammar is provided below. You can test it with https://peggyjs.org/online.html.", - ); - console.log({ - info: "Copy the grammar by right-clicking on the property grammar and copying it as a string. Then, paste it in https://peggyjs.org/online.html.", - grammar: grammar, - }); } return parser; } function generateGrammar(options: ParserOptions): string { - const close_rules_list: string[] = []; + // Contains the grammar for cloze cards + let clozes_grammar = ""; - if (options.convertHighlightsToClozes) close_rules_list.push("close_equal"); - if (options.convertBoldTextToClozes) close_rules_list.push("close_star"); - if (options.convertCurlyBracketsToClozes) close_rules_list.push("close_bracket"); + // An array contianing the types of cards enabled by the user + const card_rules_list: string[] = ["html_comment", "tilde_code", "backprime_code"]; - const close_rules = close_rules_list.join(" / "); + // Include reversed inline flashcards rule only if the user provided a non-empty marker for reversed inline flashcards + if (options.singleLineCardSeparator.trim() !== "") card_rules_list.push("inline_rev_card"); - return `{ -// The fallback case is important if we want to test the rules with https://peggyjs.org/online.html -const CardTypeFallBack = { - SingleLineBasic: 0, - SingleLineReversed: 1, - MultiLineBasic: 2, - MultiLineReversed: 3, - Cloze: 4, -}; - -// The fallback case is important if we want to test the rules with https://peggyjs.org/online.html -const createParsedQuestionInfoFallBack = (cardType, text, firstLineNum, lastLineNum) => { - return {cardType, text, firstLineNum, lastLineNum}; -}; - -const CardType = options.CardType ? options.CardType : CardTypeFallBack; -const createParsedQuestionInfo = options.createParsedQuestionInfo ? options.createParsedQuestionInfo : createParsedQuestionInfoFallBack; - -function filterBlocks(b) { - return b.filter( (d) => d !== null ) + // Include inline flashcards rule only if the user provided a non-empty marker for inline flashcards + if (options.singleLineCardSeparator.trim() !== "") card_rules_list.push("inline_card"); + + // Include reversed multiline flashcards rule only if the user provided a non-empty marker for reversed multiline flashcards + if (options.multilineReversedCardSeparator.trim() !== "") + card_rules_list.push("multiline_rev_card"); + + // Include multiline flashcards rule only if the user provided a non-empty marker for multiline flashcards + if (options.multilineCardSeparator.trim() !== "") card_rules_list.push("multiline_card"); + + const cloze_rules_list: string[] = []; + if (options.convertHighlightsToClozes) cloze_rules_list.push("cloze_equal"); + if (options.convertBoldTextToClozes) cloze_rules_list.push("cloze_star"); + if (options.convertCurlyBracketsToClozes) cloze_rules_list.push("cloze_bracket"); + + // Include cloze cards only if the user enabled at least one type of cloze cards + if (cloze_rules_list.length > 0) { + card_rules_list.push("cloze_card"); + const cloze_rules = cloze_rules_list.join(" / "); + clozes_grammar = ` +cloze_card += $(multiline_before_cloze? cloze_line (multiline_after_cloze)? (newline annotation)?) { + return createParsedQuestionInfo(CardType.Cloze,text().trimEnd(),location().start.line-1,location().end.line-1); } + +cloze_line += ((!cloze_text (inline_code / non_newline))* cloze_text) text_line_nonterminated? + +multiline_before_cloze += (!cloze_line nonempty_text_line)+ + +multiline_after_cloze += e:(!(newline separator_line) text_line1)+ + +cloze_text += ${cloze_rules} + +cloze_equal += cloze_mark_equal (!cloze_mark_equal non_newline)+ cloze_mark_equal + +cloze_mark_equal += "==" + +cloze_star += cloze_mark_star (!cloze_mark_star non_newline)+ cloze_mark_star + +cloze_mark_star += "**" + +cloze_bracket += cloze_mark_bracket_open (!cloze_mark_bracket_close non_newline)+ cloze_mark_bracket_close + +cloze_mark_bracket_open += "{{" + +cloze_mark_bracket_close += "}}" +`; + } + + // Important: we need to include `loose_line` rule to detect any other loose line. + // Otherwise, we get a syntax error because the parser is likely not able to reach the end + // of the file, as it may encounter loose lines, which it would not know how to handle. + card_rules_list.push("loose_line"); + + const card_rules = card_rules_list.join(" / "); + + return `{ + // The fallback case is important if we want to test the rules with https://peggyjs.org/online.html + const CardTypeFallBack = { + SingleLineBasic: 0, + SingleLineReversed: 1, + MultiLineBasic: 2, + MultiLineReversed: 3, + Cloze: 4, + }; + + // The fallback case is important if we want to test the rules with https://peggyjs.org/online.html + const createParsedQuestionInfoFallBack = (cardType, text, firstLineNum, lastLineNum) => { + return {cardType, text, firstLineNum, lastLineNum}; + }; + + const CardType = options.CardType ? options.CardType : CardTypeFallBack; + const createParsedQuestionInfo = options.createParsedQuestionInfo ? options.createParsedQuestionInfo : createParsedQuestionInfoFallBack; + + function filterBlocks(b) { + return b.filter( (d) => d !== null ) + } } main @@ -111,7 +180,7 @@ main /* The input text to the parser contains arbitrary text, not just card definitions. Hence we fallback to matching on loose_line. The result from loose_line is filtered out by filterBlocks() */ block -= html_comment / tilde_code / backprime_code / inline_rev_card / inline_card / multiline_rev_card / multiline_card / close_card / loose_line += ${card_rules} html_comment = $("" (html_comment / .))* "-->" newline?) { @@ -139,7 +208,7 @@ inline_rev_card inline_rev = left:(!inline_rev_mark (inline_code / non_newline))+ inline_rev_mark right:text_till_newline (newline annotation)? { return createParsedQuestionInfo(CardType.SingleLineReversed,text(),location().start.line-1,location().end.line-1); - } +} multiline_card = c:multiline separator_line { @@ -196,43 +265,7 @@ multiline_rev_before multiline_rev_after = $(!separator_line text_line)+ -close_card -= $(multiline_before_close? close_line (multiline_after_close)? (newline annotation)?) { - return createParsedQuestionInfo(CardType.Cloze,text().trimEnd(),location().start.line-1,location().end.line-1); -} - -close_line -= ((!close_text non_newline)* close_text) text_line_nonterminated? - -multiline_before_close -= (!close_line nonempty_text_line)+ - -multiline_after_close -= e:(!(newline separator_line) text_line1)+ - -close_text -= ${close_rules} - -close_equal -= close_mark_equal (!close_mark_equal non_newline)+ close_mark_equal - -close_mark_equal -= "==" - -close_star -= close_mark_star (!close_mark_star non_newline)+ close_mark_star - -close_mark_star -= "**" - -close_bracket -= close_mark_bracket_open (!close_mark_bracket_close non_newline)+ close_mark_bracket_close - -close_mark_bracket_open -= "{{" - -close_mark_bracket_close -= "}}" +${clozes_grammar} inline_mark = "${options.singleLineCardSeparator}" @@ -343,7 +376,7 @@ export function parseEx(text: string, options: ParserOptions): ParsedQuestionInf let cards: ParsedQuestionInfo[] = []; try { - if (!options) throw Error("No parser options provided."); + if (!options) throw new Error("No parser options provided."); const parser: Parser = generateParser(options); @@ -370,8 +403,7 @@ export function parseEx(text: string, options: ParserOptions): ParsedQuestionInf } if (debugParser) { - console.log("Parsed cards:"); - console.log(cards); + console.log("Parsed cards:\n", cards); } return cards; diff --git a/tests/unit/parser.test.ts b/tests/unit/parser.test.ts index d830318c..21c5960f 100644 --- a/tests/unit/parser.test.ts +++ b/tests/unit/parser.test.ts @@ -15,12 +15,11 @@ const parserOptions: ParserOptions = { /** * This function is a small wrapper around parseEx used for testing only. + * It generates a parser each time, overwriting the default one. * Created when the actual parser changed from returning [CardType, string, number, number] to ParsedQuestionInfo. * It's purpose is to minimise changes to all the test cases here during the parser()->parserEx() change. */ function parse(text: string, options: ParserOptions): [CardType, string, number, number][] { - // for testing purposes, generate parser each time, overwriting the default one - const list: ParsedQuestionInfo[] = parseEx(text, options); const result: [CardType, string, number, number][] = []; for (const item of list) { @@ -30,6 +29,7 @@ function parse(text: string, options: ParserOptions): [CardType, string, number, } test("Test parsing of single line basic cards", () => { + // standard symbols expect(parse("Question::Answer", parserOptions)).toEqual([ [CardType.SingleLineBasic, "Question::Answer", 0, 0], ]); @@ -49,9 +49,50 @@ test("Test parsing of single line basic cards", () => { expect(parse("#flashcards/science Question ::Answer", parserOptions)).toEqual([ [CardType.SingleLineBasic, "#flashcards/science Question ::Answer", 0, 0], ]); + + // custom symbols + expect( + parse("Question&&Answer", { + singleLineCardSeparator: "&&", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "---", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([[CardType.SingleLineBasic, "Question&&Answer", 0, 0]]); + expect( + parse("Question=Answer", { + singleLineCardSeparator: "=", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "---", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([[CardType.SingleLineBasic, "Question=Answer", 0, 0]]); + + // empty string or whitespace character provided + expect( + parse("Question::Answer", { + singleLineCardSeparator: "", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "---", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([]); }); test("Test parsing of single line reversed cards", () => { + // standard symbols expect(parse("Question:::Answer", parserOptions)).toEqual([ [CardType.SingleLineReversed, "Question:::Answer", 0, 0], ]); @@ -62,9 +103,38 @@ test("Test parsing of single line reversed cards", () => { [CardType.SingleLineReversed, "Q1:::A1", 2, 2], [CardType.SingleLineReversed, "Q2::: A2", 3, 3], ]); + + // custom symbols + expect( + parse("Question&&&Answer", { + singleLineCardSeparator: "::", + singleLineReversedCardSeparator: "&&&", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "---", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([[CardType.SingleLineReversed, "Question&&&Answer", 0, 0]]); + + // empty string or whitespace character provided + expect( + parse("Question:::Answer", { + singleLineCardSeparator: ">", + singleLineReversedCardSeparator: " ", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "---", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([]); }); test("Test parsing of multi line basic cards", () => { + // standard symbols expect(parse("Question\n?\nAnswer", parserOptions)).toEqual([ [CardType.MultiLineBasic, "Question\n?\nAnswer", 0, 2], ]); @@ -84,12 +154,7 @@ test("Test parsing of multi line basic cards", () => { [CardType.MultiLineBasic, "Question\n?\nAnswer line 1\nAnswer line 2", 0, 3], ]); expect(parse("#Title\n\nLine0\nQ1\n?\nA1\nAnswerExtra\n\nQ2\n?\nA2", parserOptions)).toEqual([ - [ - CardType.MultiLineBasic, - "Line0\nQ1\n?\nA1\nAnswerExtra", - /* Line0 */ 2, - /* AnswerExtra */ 6, - ], + [CardType.MultiLineBasic, "Line0\nQ1\n?\nA1\nAnswerExtra", 2, 6], [CardType.MultiLineBasic, "Q2\n?\nA2", 8, 10], ]); expect(parse("#flashcards/tag-on-previous-line\nQuestion\n?\nAnswer", parserOptions)).toEqual([ @@ -148,9 +213,38 @@ test("Test parsing of multi line basic cards", () => { 10, ], ]); + + // custom symbols + expect( + parse("Question\n@@\nAnswer\n\nsfdg", { + singleLineCardSeparator: "::", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "@@", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([[CardType.MultiLineBasic, "Question\n@@\nAnswer", 0, 2]]); + + // empty string or whitespace character provided + expect( + parse("Question\n?\nAnswer", { + singleLineCardSeparator: "::", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "\n", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "---", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([]); }); test("Test parsing of multi line reversed cards", () => { + // standard symbols expect(parse("Question\n??\nAnswer", parserOptions)).toEqual([ [CardType.MultiLineReversed, "Question\n??\nAnswer", 0, 2], ]); @@ -161,12 +255,7 @@ test("Test parsing of multi line reversed cards", () => { [CardType.MultiLineReversed, "Question\n??\nAnswer line 1\nAnswer line 2", 0, 3], ]); expect(parse("#Title\n\nLine0\nQ1\n??\nA1\nAnswerExtra\n\nQ2\n??\nA2", parserOptions)).toEqual([ - [ - CardType.MultiLineReversed, - "Line0\nQ1\n??\nA1\nAnswerExtra", - /* Line0 */ 2, - /* AnswerExtra */ 6, - ], + [CardType.MultiLineReversed, "Line0\nQ1\n??\nA1\nAnswerExtra", 2, 6], [CardType.MultiLineReversed, "Q2\n??\nA2", 8, 10], ]); expect( @@ -199,6 +288,34 @@ test("Test parsing of multi line reversed cards", () => { [CardType.MultiLineBasic, "Question 1\n?\nAnswer line 1\nAnswer line 2", 0, 4], [CardType.MultiLineReversed, "Question 2\n??\nAnswer line 1\nAnswer line 2", 6, 9], ]); + + // custom symbols + expect( + parse("Question\n@@@\nAnswer\n---", { + singleLineCardSeparator: "::", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "@@", + multilineReversedCardSeparator: "@@@", + multilineCardEndMarker: "---", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([[CardType.MultiLineReversed, "Question\n@@@\nAnswer", 0, 2]]); + + // empty string or whitespace character provided + expect( + parse("Question\n??\nAnswer", { + singleLineCardSeparator: "::", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "\t", + multilineCardEndMarker: "---", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([]); }); test("Test parsing of cloze cards", () => { @@ -228,6 +345,7 @@ test("Test parsing of cloze cards", () => { expect(parse("srdf ==", parserOptions)).toEqual([]); expect(parse("lorem ipsum ==p\ndolor won==", parserOptions)).toEqual([]); expect(parse("lorem ipsum ==dolor won=", parserOptions)).toEqual([]); + // ==highlights== turned off expect( parse("cloze ==deletion== test", { @@ -238,7 +356,7 @@ test("Test parsing of cloze cards", () => { multilineCardEndMarker: "", convertHighlightsToClozes: false, convertBoldTextToClozes: true, - convertCurlyBracketsToClozes: false, + convertCurlyBracketsToClozes: true, }), ).toEqual([]); @@ -268,6 +386,7 @@ test("Test parsing of cloze cards", () => { expect(parse("srdf **", parserOptions)).toEqual([]); expect(parse("lorem ipsum **p\ndolor won**", parserOptions)).toEqual([]); expect(parse("lorem ipsum **dolor won*", parserOptions)).toEqual([]); + // **bolded** turned off expect( parse("cloze **deletion** test", { @@ -278,15 +397,56 @@ test("Test parsing of cloze cards", () => { multilineCardEndMarker: "", convertHighlightsToClozes: true, convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: true, + }), + ).toEqual([]); + + // {{curly}} + expect(parse("cloze {{deletion}} test", parserOptions)).toEqual([ + [CardType.Cloze, "cloze {{deletion}} test", 0, 0], + ]); + expect(parse("cloze {{deletion}} test\n", parserOptions)).toEqual([ + [CardType.Cloze, "cloze {{deletion}} test\n", 0, 1], + ]); + expect(parse("cloze {{deletion}} test ", parserOptions)).toEqual([ + [CardType.Cloze, "cloze {{deletion}} test ", 0, 0], + ]); + expect(parse("{{this}} is a {{deletion}}\n", parserOptions)).toEqual([ + [CardType.Cloze, "{{this}} is a {{deletion}}", 0, 0], + ]); + expect( + parse( + "some text before\n\na deletion on\nsuch {{wow}}\n\n" + + "many text\nsuch surprise {{wow}} more {{text}}\nsome text after\n\nHmm", + parserOptions, + ), + ).toEqual([ + [CardType.Cloze, "a deletion on\nsuch {{wow}}", 2, 3], + [CardType.Cloze, "many text\nsuch surprise {{wow}} more {{text}}\nsome text after", 5, 7], + ]); + expect(parse("srdf {{", parserOptions)).toEqual([]); + expect(parse("srdf }}", parserOptions)).toEqual([]); + expect(parse("lorem ipsum {{p\ndolor won}}", parserOptions)).toEqual([]); + expect(parse("lorem ipsum {{dolor won}", parserOptions)).toEqual([]); + + // {{curly}} turned off + expect( + parse("cloze {{deletion}} test", { + singleLineCardSeparator: "::", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "", + convertHighlightsToClozes: true, + convertBoldTextToClozes: true, convertCurlyBracketsToClozes: false, }), ).toEqual([]); - // both + // combo expect(parse("cloze **deletion** test ==another deletion==!", parserOptions)).toEqual([ [CardType.Cloze, "cloze **deletion** test ==another deletion==!", 0, 0], ]); - expect( parse( "Test 1\nTest 2\nThis is a close with ===secret=== text.\nWith this extra lines\n\nAnd more here.\nAnd even more.\n\n---\n\nTest 3\nTest 4\nThis is a close with ===super secret=== text.\nWith this extra lines\n\nAnd more here.\nAnd even more.\n\n---\n\nHere is some more text.", @@ -315,6 +475,20 @@ test("Test parsing of cloze cards", () => { 17, ], ]); + + // all disabled + expect( + parse("cloze {{deletion}} test and **deletion** ==another deletion==!", { + singleLineCardSeparator: "::", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "", + convertHighlightsToClozes: false, + convertBoldTextToClozes: false, + convertCurlyBracketsToClozes: false, + }), + ).toEqual([]); }); test("Test parsing of a mix of card types", () => { @@ -340,13 +514,28 @@ test("Test parsing of a mix of card types", () => { CardType.MultiLineReversed, "Donec dapibus ullamcorper aliquam.\n??\nDonec dapibus ullamcorper aliquam.\n", 8, - 11 /* */, + 11, ], ]); }); -test("Test codeblocks", () => { - // no blank lines +test("Test parsing cards with codeblocks", () => { + // `inline` + expect( + parse( + "my inline question containing `some inline code` in it::and this is answer possibly containing `inline` code.", + parserOptions, + ), + ).toEqual([ + [ + CardType.SingleLineBasic, + "my inline question containing `some inline code` in it::and this is answer possibly containing `inline` code.", + 0, + 0, + ], + ]); + + // ```block```, no blank lines expect( parse( "How do you ... Python?\n?\n" + @@ -359,11 +548,11 @@ test("Test codeblocks", () => { "How do you ... Python?\n?\n" + "```\nprint('Hello World!')\nprint('Howdy?')\nlambda x: x[0]\n```", 0, - 6 /* ``` */, + 6, ], ]); - // with blank lines + // ```block```, with blank lines expect( parse( "How do you ... Python?\n?\n" + @@ -376,11 +565,11 @@ test("Test codeblocks", () => { "How do you ... Python?\n?\n" + "```\nprint('Hello World!')\n\n\nprint('Howdy?')\n\nlambda x: x[0]\n```", 0, - 9 /* ``` */, + 9, ], ]); - // general Markdown syntax + // nested markdown expect( parse( "Nested Markdown?\n?\n" + @@ -409,12 +598,14 @@ test("Test codeblocks", () => { "~~~\n" + "````", 0, - 12 /* ``` */, + 12, ], ]); }); test("Test not parsing cards in HTML comments", () => { + expect(parse("", parserOptions)).toEqual([]); + expect(parse("", parserOptions)).toEqual([]); expect( parse("\n-->", parserOptions), ).toEqual([]); @@ -426,6 +617,46 @@ test("Test not parsing cards in HTML comments", () => { ).toEqual([]); expect(parse("", parserOptions)).toEqual([]); expect(parse("", parserOptions)).toEqual([]); + expect(parse("", parserOptions)).toEqual([]); +}); + +test("Test not parsing 'cards' in codeblocks", () => { + // block + expect(parse("```\nCodeblockq::CodeblockA\n```", parserOptions)).toEqual([]); + expect(parse("```\nCodeblockq:::CodeblockA\n```", parserOptions)).toEqual([]); + expect( + parse("# Title\n\n```markdown\nsome ==highlighted text==!\n```\n\nmore!", parserOptions), + ).toEqual([]); + expect( + parse("# Title\n```markdown\nsome **bolded text**!\n```\n\nmore!", parserOptions), + ).toEqual([]); + expect(parse("# Title\n\n```\nfoo = {{'a': 2}}\n```\n\nmore!", parserOptions)).toEqual([]); + + // inline + expect(parse("`Inlineq::InlineA`", parserOptions)).toEqual([]); + expect( + parse("# Title\n`if (a & b) {}`\nmore!", { + singleLineCardSeparator: "&", + singleLineReversedCardSeparator: ":::", + multilineCardSeparator: "?", + multilineReversedCardSeparator: "??", + multilineCardEndMarker: "", + convertHighlightsToClozes: true, + convertBoldTextToClozes: true, + convertCurlyBracketsToClozes: true, + }), + ).toEqual([]); + expect(parse("# Title\n`if a == b && c == d {}`\nmore!", parserOptions)).toEqual([]); + expect(parse("# Title\n\n`z = a ** b + 5 ** 7`\n\nmore!", parserOptions)).toEqual([]); + expect(parse("# Title\n`foo = {{'a': 2}}`\n\nmore!", parserOptions)).toEqual([]); + + // combo + expect( + parse( + "Question::Answer\n\n```\nCodeblockq::CodeblockA\n```\n\n`Inlineq::InlineA`\n", + parserOptions, + ), + ).toEqual([[CardType.SingleLineBasic, "Question::Answer", 0, 0]]); }); test("Unexpected Error case", () => {