Skip to content

Commit 595785a

Browse files
authored
Merge pull request #3192 from obsidian-tasks-group/refactor-emoji-parsing
Refactor emoji parsing
2 parents cae0faf + 8916f19 commit 595785a

File tree

4 files changed

+113
-18
lines changed

4 files changed

+113
-18
lines changed

src/TaskSerializer/DefaultTaskSerializer.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ export const taskIdRegex = /[a-zA-Z0-9-_]+/;
5555
// The allowed characters in a comma-separated sequence of task ids:
5656
export const taskIdSequenceRegex = new RegExp(taskIdRegex.source + '( *, *' + taskIdRegex.source + ' *)*');
5757

58+
function dateFieldRegex(symbols: string) {
59+
return fieldRegex(symbols, '(\\d{4}-\\d{2}-\\d{2})');
60+
}
61+
62+
function fieldRegex(symbols: string, valueRegexString: string) {
63+
let source = symbols;
64+
if (valueRegexString !== '') {
65+
source += ' *' + valueRegexString;
66+
}
67+
source += '$';
68+
return new RegExp(source, 'u');
69+
}
70+
5871
/**
5972
* A symbol map for obsidian-task's default task style.
6073
* Uses emojis to concisely convey meaning
@@ -83,17 +96,17 @@ export const DEFAULT_SYMBOLS: DefaultTaskSerializerSymbols = {
8396
// The following regex's end with `$` because they will be matched and
8497
// removed from the end until none are left.
8598
// \uFE0F? allows an optional Variant Selector 16 on emojis.
86-
priorityRegex: /([🔺🔼🔽])\uFE0F?$/u,
87-
startDateRegex: /🛫 *(\d{4}-\d{2}-\d{2})$/u,
88-
createdDateRegex: / *(\d{4}-\d{2}-\d{2})$/u,
89-
scheduledDateRegex: /[] *(\d{4}-\d{2}-\d{2})$/u,
90-
dueDateRegex: /[📅📆🗓] *(\d{4}-\d{2}-\d{2})$/u,
91-
doneDateRegex: / *(\d{4}-\d{2}-\d{2})$/u,
92-
cancelledDateRegex: / *(\d{4}-\d{2}-\d{2})$/u,
93-
recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu,
94-
onCompletionRegex: /🏁 *([a-zA-Z]+)$/iu,
95-
dependsOnRegex: new RegExp('⛔\uFE0F? *(' + taskIdSequenceRegex.source + ')$', 'iu'),
96-
idRegex: new RegExp('🆔 *(' + taskIdRegex.source + ')$', 'iu'),
99+
priorityRegex: fieldRegex('([🔺⏫🔼🔽⏬])\uFE0F?', ''),
100+
startDateRegex: dateFieldRegex('🛫'),
101+
createdDateRegex: dateFieldRegex('➕'),
102+
scheduledDateRegex: dateFieldRegex('[⏳⌛]'),
103+
dueDateRegex: dateFieldRegex('[📅📆🗓]'),
104+
doneDateRegex: dateFieldRegex('✅'),
105+
cancelledDateRegex: dateFieldRegex('❌'),
106+
recurrenceRegex: fieldRegex('🔁', '([a-zA-Z0-9, !]+)'),
107+
onCompletionRegex: fieldRegex('🏁', '([a-zA-Z]+)'),
108+
dependsOnRegex: fieldRegex('⛔\uFE0F?', '(' + taskIdSequenceRegex.source + ')'),
109+
idRegex: fieldRegex('🆔', '(' + taskIdRegex.source + ')'),
97110
},
98111
} as const;
99112

src/lib/StringHelpers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
11
export function capitalizeFirstLetter(text: string): string {
22
return text.charAt(0).toUpperCase() + text.slice(1);
33
}
4+
5+
/**
6+
* Escape non-visible Variation Selectors characters in string.
7+
* Converts them into their Unicode escape sequences for explicit visibility.
8+
*/
9+
export function escapeInvisibleCharacters(input: string): string {
10+
return input.replace(
11+
/[\uFE00-\uFE0F]/g, // Matches Variation Selectors
12+
(char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`,
13+
);
14+
}

tests/TaskSerializer/DefaultTaskSerializer.test.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { TaskBuilder } from '../TestingTools/TaskBuilder';
1414
import { OnCompletion } from '../../src/Task/OnCompletion';
1515
import { Priority } from '../../src/Task/Priority';
16+
import { escapeInvisibleCharacters } from '../../src/lib/StringHelpers';
1617

1718
jest.mock('obsidian');
1819
window.moment = moment;
@@ -57,23 +58,25 @@ describe('validate emoji regular expressions', () => {
5758
throw new Error(`Unexpected value for ${key}: Not a regular expression.`);
5859
}
5960
});
60-
// Concatenate all entries into a single string
61-
return regexDetails.join('\n');
61+
// Concatenate all entries into a single string, with any Variation Selectors made visible
62+
return escapeInvisibleCharacters('\n' + regexDetails.join('\n') + '\n');
6263
}
6364

6465
it('regular expressions should have expected source', () => {
6566
expect(generateRegexApprovalTest()).toMatchInlineSnapshot(`
66-
"priorityRegex: /([🔺⏫🔼🔽⏬])\\uFE0F?$/u
67+
"
68+
priorityRegex: /([🔺⏫🔼🔽⏬])\\ufe0f?$/u
6769
startDateRegex: /🛫 *(\\d{4}-\\d{2}-\\d{2})$/u
6870
createdDateRegex: /➕ *(\\d{4}-\\d{2}-\\d{2})$/u
6971
scheduledDateRegex: /[⏳⌛] *(\\d{4}-\\d{2}-\\d{2})$/u
7072
dueDateRegex: /[📅📆🗓] *(\\d{4}-\\d{2}-\\d{2})$/u
7173
doneDateRegex: /✅ *(\\d{4}-\\d{2}-\\d{2})$/u
7274
cancelledDateRegex: /❌ *(\\d{4}-\\d{2}-\\d{2})$/u
73-
recurrenceRegex: /🔁 ?([a-zA-Z0-9, !]+)$/iu
74-
onCompletionRegex: /🏁 *([a-zA-Z]+)$/iu
75-
dependsOnRegex: /⛔️? *([a-zA-Z0-9-_]+( *, *[a-zA-Z0-9-_]+ *)*)$/iu
76-
idRegex: /🆔 *([a-zA-Z0-9-_]+)$/iu"
75+
recurrenceRegex: /🔁 *([a-zA-Z0-9, !]+)$/u
76+
onCompletionRegex: /🏁 *([a-zA-Z]+)$/u
77+
dependsOnRegex: /⛔\\ufe0f? *([a-zA-Z0-9-_]+( *, *[a-zA-Z0-9-_]+ *)*)$/u
78+
idRegex: /🆔 *([a-zA-Z0-9-_]+)$/u
79+
"
7780
`);
7881
});
7982
});

tests/lib/StringHelpers.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { escapeInvisibleCharacters } from '../../src/lib/StringHelpers';
2+
3+
describe('StringHelpers', () => {
4+
it('escapeInvisibleCharacters', () => {
5+
const value = `
6+
// Variation Selectors
7+
⛔\uFE00 // VS1
8+
⛔\uFE01 // VS2
9+
⛔\uFE02 // VS3
10+
⛔\uFE03 // VS4
11+
⛔\uFE04 // VS5
12+
⛔\uFE05 // VS6
13+
⛔\uFE06 // VS7
14+
⛔\uFE07 // VS8
15+
⛔\uFE08 // VS9
16+
⛔\uFE09 // VS10
17+
⛔\uFE0A // VS11
18+
⛔\uFE0B // VS12
19+
⛔\uFE0C // VS13
20+
⛔\uFE0D // VS14
21+
⛔\uFE0E // VS15
22+
⛔\uFE0F // VS16
23+
`;
24+
expect(value).toMatchInlineSnapshot(`
25+
"
26+
// Variation Selectors
27+
⛔︀ // VS1
28+
⛔︁ // VS2
29+
⛔︂ // VS3
30+
⛔︃ // VS4
31+
⛔︄ // VS5
32+
⛔︅ // VS6
33+
⛔︆ // VS7
34+
⛔︇ // VS8
35+
⛔︈ // VS9
36+
⛔︉ // VS10
37+
⛔︊ // VS11
38+
⛔︋ // VS12
39+
⛔︌ // VS13
40+
⛔︍ // VS14
41+
⛔︎ // VS15
42+
⛔️ // VS16
43+
"
44+
`);
45+
46+
expect(escapeInvisibleCharacters(value)).toMatchInlineSnapshot(`
47+
"
48+
// Variation Selectors
49+
⛔\\ufe00 // VS1
50+
⛔\\ufe01 // VS2
51+
⛔\\ufe02 // VS3
52+
⛔\\ufe03 // VS4
53+
⛔\\ufe04 // VS5
54+
⛔\\ufe05 // VS6
55+
⛔\\ufe06 // VS7
56+
⛔\\ufe07 // VS8
57+
⛔\\ufe08 // VS9
58+
⛔\\ufe09 // VS10
59+
⛔\\ufe0a // VS11
60+
⛔\\ufe0b // VS12
61+
⛔\\ufe0c // VS13
62+
⛔\\ufe0d // VS14
63+
⛔\\ufe0e // VS15
64+
⛔\\ufe0f // VS16
65+
"
66+
`);
67+
});
68+
});

0 commit comments

Comments
 (0)