Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ move.sh

# Test-vault
docs/test-vault/.obsidian/workspace

# Coverage results
coverage/
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
},
"dependencies": {
"@types/showdown": "^1.9.3",
"showdown": "^1.9.1"
"showdown": "^1.9.1",
"ts-dedent": "^2.2.0"
}
}
41 changes: 26 additions & 15 deletions src/conf/regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,10 @@ export class Regex {
this.headingsRegex = /^ {0,3}(#{1,6}) +([^\n]+?) ?((?: *#\S+)*) *$/gim;

// Supported images https://publish.obsidian.md/help/How+to/Embed+files
this.wikiImageLinks =
/!\[\[(.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff)).*?\]\]/gim;
this.markdownImageLinks =
/!\[\]\((.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff)).*?\)/gim;
this.wikiImageLinks = /!\[\[(.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff)).*?\]\]/gim;
this.markdownImageLinks = /!\[\]\((.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff)).*?\)/gim;

this.wikiAudioLinks =
/!\[\[(.*\.(?:mp3|webm|wav|m4a|ogg|3gp|flac)).*?\]\]/gim;
this.wikiAudioLinks = /!\[\[(.*\.(?:mp3|webm|wav|m4a|ogg|3gp|flac)).*?\]\]/gim;

// https://regex101.com/r/eqnJeW/1
this.obsidianCodeBlock = /(?:```(?:.*?\n?)+?```)(?:\n|$)/gim;
Expand All @@ -53,8 +50,7 @@ export class Regex {
this.cardsToDelete = /^\s*(?:\n)(?:\^(\d{13}))(?:\n\s*?)?/gm;

// https://regex101.com/r/WxuFI2/1
this.globalTagsSplitter =
/\[\[(.*?)\]\]|#([\p{L}\d:\-_/]+)|([\p{L}\d:\-_/]+)/gimu;
this.globalTagsSplitter = /\[\[(.*?)\]\]|#([\p{L}\d:\-_/]+)|([\p{L}\d:\-_/]+)/gimu;
this.tagHierarchy = /\//gm;

// Cards
Expand All @@ -66,17 +62,31 @@ export class Regex {
"(?:[/-]reverse)?)((?: *#[\\p{Number}\\p{Letter}\\-\\/_]+)*) *?\\n+((?:[^\\n]\\n?)*?(?=\\^\\d{13}|$))(?:\\^(\\d{13}))?";
this.flashscardsWithTag = new RegExp(str, flags);

// https://regex101.com/r/8wmOo8/1
const sepLongest = settings.inlineSeparator.length >= settings.inlineSeparatorReverse.length ? settings.inlineSeparator : settings.inlineSeparatorReverse;
const sepShortest = settings.inlineSeparator.length < settings.inlineSeparatorReverse.length ? settings.inlineSeparator : settings.inlineSeparatorReverse;
// https://regex101.com/r/8wmOo8/2
const sepLongest =
settings.inlineSeparator.length >= settings.inlineSeparatorReverse.length
? settings.inlineSeparator
: settings.inlineSeparatorReverse;
const sepShortest =
settings.inlineSeparator.length < settings.inlineSeparatorReverse.length
? settings.inlineSeparator
: settings.inlineSeparatorReverse;
// sepLongest is the longest between the inlineSeparator and the inlineSeparatorReverse because if the order is ::|::: then always the first will be matched
// sepShortest is the shortest
if (settings.inlineID) {
str =
"( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(" + sepLongest + "|" + sepShortest + ") ?(.+?)((?: *#[\\p{Letter}\\-\\/_]+)+)?(?:\\s+\\^(\\d{13})|$)";
"( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d\\.|[-+*]|#{1,6}))?(.+?) ?(" +
sepLongest +
"|" +
sepShortest +
") ?(.+?)((?: *#[\\p{Letter}\\-\\/_]+)+)?(?:\\s+\\^(\\d{13})|$)";
} else {
str =
"( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(" + sepLongest + "|" + sepShortest + ") ?(.+?)((?: *#[\\p{Letter}\\-\\/_]+)+|$)(?:\\n\\^(\\d{13}))?";
"( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d\\.|[-+*]|#{1,6}))?(.+?) ?(" +
sepLongest +
"|" +
sepShortest +
") ?(.+?)((?: *#[\\p{Letter}\\-\\/_]+)+|$)(?:\\n\\^(\\d{13}))?";
}
this.cardsInlineStyle = new RegExp(str, flags);

Expand All @@ -89,9 +99,10 @@ export class Regex {

// https://regex101.com/r/cgtnLf/1

str = "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.*?(==.+?==|\\{.+?\\}).*?)((?: *#[\\w\\-\\/_]+)+|$)(?:\n\\^(\\d{13}))?"
str =
"( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.*?(==.+?==|\\{.+?\\}).*?)((?: *#[\\w\\-\\/_]+)+|$)(?:\n\\^(\\d{13}))?";
this.cardsClozeWholeLine = new RegExp(str, flags);

this.singleClozeCurly = /((?:{)(?:(\d):?)?(.+?)(?:}))/g;
this.singleClozeHighlight = /((?:==)(.+?)(?:==))/g;

Expand Down
90 changes: 90 additions & 0 deletions tests/regex.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import dedent from "ts-dedent";
import { Regex } from "../src/conf/regex";

describe("Regex class unit tests", () => {
describe("inline card pattern", () => {
it("should work with default settings", () => {
const regex = new Regex({
contextAwareMode: true,
sourceSupport: false,
codeHighlightSupport: false,
inlineID: false,
contextSeparator: " > ",
deck: "Default",
folderBasedDeck: true,
flashcardsTag: "card",
inlineSeparator: "::",
inlineSeparatorReverse: ":::",
defaultAnkiTag: "obsidian",
ankiConnectPermission: false,
});

type Match = [string, string];

const candidates: [string, Match[]][] = [
["Question :: Answer", [["Question", "Answer"]]],
// FIXME: should be "Question" instead of " Question"
// ["# Question :: Answer", [[" Question", "Answer"]]],
// FIXME: should not match? (tag then separator)
// ["#Question :: Answer", []],
["Answer ::: Question", [["Answer", "Question"]]],
["Question :: Answer ^123456789", [["Question", "Answer ^123456789"]]],
["Question :: Answer ^123456789\n^123456789", [["Question", "Answer ^123456789"]]],
// FIXME: should be "The question" instead of " The question"
["## Question #card\n- The question :: Answer", [[" The question", "Answer"]]],
["No match!", []],
// FIXME: should be "Question 1" instead of " Question 1"
[
dedent`
1. Question 1 :: Answer 1
2. Question 2 :: Answer 2
3. Question 3 :: Answer 3`,
[
[" Question 1", "Answer 1"],
[" Question 2", "Answer 2"],
[" Question 3", "Answer 3"],
],
],
// FIXME: should be "Question 1" instead of " Question 1"
[
dedent`
- Question 1 :: Answer 1
- Question 2 :: Answer 2
- Question 3 :: Answer 3`,
[
[" Question 1", "Answer 1"],
[" Question 2", "Answer 2"],
[" Question 3", "Answer 3"],
],
],
[
dedent`
1. Item 1 :: A
2. Item 2 :: B
- Item 2a :: Ba
- Item 2b :: Bb`,
[
[" Item 1", "A"],
[" Item 2", "B"],
[" Item 2a", "Ba"],
[" Item 2b", "Bb"],
],
],
// FIXME: should not match?
// ["no :::: match", []],
["2000 :: answer", [["2000", "answer"]]],
];

for (const [input, expected] of candidates) {
const matches = [...input.matchAll(regex.cardsInlineStyle)];

expect(matches.length).toEqual(expected.length);
expected.forEach((exp, i) => {
const match = matches[i];
expect(match[2]).toEqual(exp[0]);
expect(match[4]).toEqual(exp[1]);
});
}
});
});
});