Skip to content

Commit 6298c08

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents c94b8d9 + 68ef5f1 commit 6298c08

File tree

5 files changed

+203
-75
lines changed

5 files changed

+203
-75
lines changed

src/custom-sort/custom-sort-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export interface RecognizedOrderValue {
4040
}
4141

4242
export type NormalizerFn = (s: string) => string | null
43+
export const IdentityNormalizerFn: NormalizerFn = (s: string) => s
4344

4445
export interface RegExpSpec {
4546
regex: RegExp

src/custom-sort/matchers.spec.ts

Lines changed: 97 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@ import {
33
getNormalizedRomanNumber,
44
prependWithZeros,
55
romanToIntStr,
6-
NumberRegex,
7-
CompoundNumberDotRegex,
8-
CompoundNumberDashRegex,
9-
RomanNumberRegex,
10-
CompoundRomanNumberDotRegex,
11-
CompoundRomanNumberDashRegex
6+
NumberRegexStr,
7+
CompoundNumberDotRegexStr,
8+
CompoundNumberDashRegexStr,
9+
RomanNumberRegexStr,
10+
CompoundRomanNumberDotRegexStr,
11+
CompoundRomanNumberDashRegexStr,
12+
WordInASCIIRegexStr,
13+
WordInAnyLanguageRegexStr
1214
} from "./matchers";
15+
import {SortingSpecProcessor} from "./sorting-spec-processor";
1316

1417
describe('Plain numbers regexp', () => {
18+
let regexp: RegExp;
19+
beforeEach(() => {
20+
regexp = new RegExp('^' + NumberRegexStr, 'i');
21+
});
1522
it.each([
1623
['', null],
1724
[' ', null],
@@ -23,7 +30,7 @@ describe('Plain numbers regexp', () => {
2330
['9', '9'],
2431
['7328964783268794325496783', '7328964783268794325496783']
2532
])('%s => %s', (s: string, out: string | null) => {
26-
const match: RegExpMatchArray | null = s.match(NumberRegex)
33+
const match: RegExpMatchArray | null = s.match(regexp)
2734
if (out) {
2835
expect(match).not.toBeNull()
2936
expect(match?.[1]).toBe(out)
@@ -34,6 +41,10 @@ describe('Plain numbers regexp', () => {
3441
})
3542

3643
describe('Plain compound numbers regexp (dot)', () => {
44+
let regexp: RegExp;
45+
beforeEach(() => {
46+
regexp = new RegExp('^' + CompoundNumberDotRegexStr, 'i');
47+
});
3748
it.each([
3849
['', null],
3950
[' ', null],
@@ -55,7 +66,7 @@ describe('Plain compound numbers regexp (dot)', () => {
5566
['56.78.-.1abc', '56.78'],
5667
['56.78-.1abc', '56.78'],
5768
])('%s => %s', (s: string, out: string | null) => {
58-
const match: RegExpMatchArray | null = s.match(CompoundNumberDotRegex)
69+
const match: RegExpMatchArray | null = s.match(regexp)
5970
if (out) {
6071
expect(match).not.toBeNull()
6172
expect(match?.[1]).toBe(out)
@@ -66,6 +77,10 @@ describe('Plain compound numbers regexp (dot)', () => {
6677
})
6778

6879
describe('Plain compound numbers regexp (dash)', () => {
80+
let regexp: RegExp;
81+
beforeEach(() => {
82+
regexp = new RegExp('^' + CompoundNumberDashRegexStr, 'i');
83+
});
6984
it.each([
7085
['', null],
7186
[' ', null],
@@ -87,7 +102,7 @@ describe('Plain compound numbers regexp (dash)', () => {
87102
['56-78-.-1abc', '56-78'],
88103
['56-78.-1abc', '56-78'],
89104
])('%s => %s', (s: string, out: string | null) => {
90-
const match: RegExpMatchArray | null = s.match(CompoundNumberDashRegex)
105+
const match: RegExpMatchArray | null = s.match(regexp)
91106
if (out) {
92107
expect(match).not.toBeNull()
93108
expect(match?.[1]).toBe(out)
@@ -98,6 +113,10 @@ describe('Plain compound numbers regexp (dash)', () => {
98113
})
99114

100115
describe('Plain Roman numbers regexp', () => {
116+
let regexp: RegExp;
117+
beforeEach(() => {
118+
regexp = new RegExp('^' + RomanNumberRegexStr, 'i');
119+
});
101120
it.each([
102121
['', null],
103122
[' ', null],
@@ -109,7 +128,7 @@ describe('Plain Roman numbers regexp', () => {
109128
['iiiii', 'iiiii'],
110129
['viviviv794325496783', 'viviviv']
111130
])('%s => %s', (s: string, out: string | null) => {
112-
const match: RegExpMatchArray | null = s.match(RomanNumberRegex)
131+
const match: RegExpMatchArray | null = s.match(regexp)
113132
if (out) {
114133
expect(match).not.toBeNull()
115134
expect(match?.[1]).toBe(out)
@@ -120,6 +139,10 @@ describe('Plain Roman numbers regexp', () => {
120139
})
121140

122141
describe('Roman compound numbers regexp (dot)', () => {
142+
let regexp: RegExp;
143+
beforeEach(() => {
144+
regexp = new RegExp('^' + CompoundRomanNumberDotRegexStr, 'i');
145+
});
123146
it.each([
124147
['', null],
125148
[' ', null],
@@ -143,7 +166,7 @@ describe('Roman compound numbers regexp (dot)', () => {
143166
['xvx.d-.iabc', 'xvx.d'],
144167
['xvx.d..iabc', 'xvx.d'],
145168
])('%s => %s', (s: string, out: string | null) => {
146-
const match: RegExpMatchArray | null = s.match(CompoundRomanNumberDotRegex)
169+
const match: RegExpMatchArray | null = s.match(regexp)
147170
if (out) {
148171
expect(match).not.toBeNull()
149172
expect(match?.[1]).toBe(out)
@@ -154,6 +177,10 @@ describe('Roman compound numbers regexp (dot)', () => {
154177
})
155178

156179
describe('Roman compound numbers regexp (dash)', () => {
180+
let regexp: RegExp;
181+
beforeEach(() => {
182+
regexp = new RegExp('^' + CompoundRomanNumberDashRegexStr, 'i');
183+
});
157184
it.each([
158185
['', null],
159186
[' ', null],
@@ -177,7 +204,65 @@ describe('Roman compound numbers regexp (dash)', () => {
177204
['xvx-d.-iabc', 'xvx-d'],
178205
['xvx-d--iabc', 'xvx-d']
179206
])('%s => %s', (s: string, out: string | null) => {
180-
const match: RegExpMatchArray | null = s.match(CompoundRomanNumberDashRegex)
207+
const match: RegExpMatchArray | null = s.match(regexp)
208+
if (out) {
209+
expect(match).not.toBeNull()
210+
expect(match?.[1]).toBe(out)
211+
} else {
212+
expect(match).toBeNull()
213+
}
214+
})
215+
})
216+
217+
describe('ASCII word regexp', () => {
218+
let regexp: RegExp;
219+
beforeEach(() => {
220+
regexp = new RegExp('^' + WordInASCIIRegexStr, 'i');
221+
});
222+
it.each([
223+
['', null],
224+
[' ', null],
225+
[' I', null], // leading spaces are not swallowed
226+
['I ', 'I'], // trailing spaces are swallowed
227+
['Abc', 'Abc'],
228+
['Sun', 'Sun'],
229+
['Hello123', 'Hello'],
230+
['John_', 'John'],
231+
['Title.', 'Title'],
232+
['Deutschstäder', 'Deutschst'],
233+
['ItalianoàèéìòùÈ', 'Italiano'],
234+
['PolskićśńĄł', 'Polski']
235+
])('%s => %s', (s: string, out: string | null) => {
236+
const match: RegExpMatchArray | null = s.match(regexp)
237+
if (out) {
238+
expect(match).not.toBeNull()
239+
expect(match?.[1]).toBe(out)
240+
} else {
241+
expect(match).toBeNull()
242+
}
243+
})
244+
})
245+
246+
describe('Unicode word regexp', () => {
247+
let regexp: RegExp;
248+
beforeEach(() => {
249+
regexp = new RegExp('^' + WordInAnyLanguageRegexStr, 'ui');
250+
});
251+
it.each([
252+
['', null],
253+
[' ', null],
254+
[' I', null], // leading spaces are not swallowed
255+
['I ', 'I'], // trailing characters are ignored in unit test
256+
['Abc', 'Abc'],
257+
['Sun', 'Sun'],
258+
['Hello123', 'Hello'],
259+
['John_', 'John'],
260+
['Title.', 'Title'],
261+
['Deutschstäder_', 'Deutschstäder'],
262+
['ItalianoàèéìòùÈ', 'ItalianoàèéìòùÈ'],
263+
['PolskićśńĄł', 'PolskićśńĄł']
264+
])('%s => %s', (s: string, out: string | null) => {
265+
const match: RegExpMatchArray | null = s.match(regexp)
181266
if (out) {
182267
expect(match).not.toBeNull()
183268
expect(match?.[1]).toBe(out)

src/custom-sort/matchers.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
1-
export const RomanNumberRegex: RegExp = /^ *([MDCLXVI]+)/i; // Roman number
2-
export const RomanNumberRegexStr: string = ' *([MDCLXVI]+)';
3-
export const CompoundRomanNumberDotRegex: RegExp = /^ *([MDCLXVI]+(?:\.[MDCLXVI]+)*)/i; // Compound Roman number with dot as separator
4-
export const CompoundRomanNumberDotRegexStr: string = ' *([MDCLXVI]+(?:\\.[MDCLXVI]+)*)';
5-
export const CompoundRomanNumberDashRegex: RegExp = /^ *([MDCLXVI]+(?:-[MDCLXVI]+)*)/i; // Compound Roman number with dash as separator
6-
export const CompoundRomanNumberDashRegexStr: string = ' *([MDCLXVI]+(?:-[MDCLXVI]+)*)';
1+
export const RomanNumberRegexStr: string = ' *([MDCLXVI]+)'; // Roman number
2+
export const CompoundRomanNumberDotRegexStr: string = ' *([MDCLXVI]+(?:\\.[MDCLXVI]+)*)';// Compound Roman number with dot as separator
3+
export const CompoundRomanNumberDashRegexStr: string = ' *([MDCLXVI]+(?:-[MDCLXVI]+)*)'; // Compound Roman number with dash as separator
74

8-
export const NumberRegex: RegExp = /^ *(\d+)/; // Plain number
9-
export const NumberRegexStr: string = ' *(\\d+)';
10-
export const CompoundNumberDotRegex: RegExp = /^ *(\d+(?:\.\d+)*)/; // Compound number with dot as separator
11-
export const CompoundNumberDotRegexStr: string = ' *(\\d+(?:\\.\\d+)*)';
12-
export const CompoundNumberDashRegex: RegExp = /^ *(\d+(?:-\d+)*)/; // Compound number with dash as separator
13-
export const CompoundNumberDashRegexStr: string = ' *(\\d+(?:-\\d+)*)';
5+
export const NumberRegexStr: string = ' *(\\d+)'; // Plain number
6+
export const CompoundNumberDotRegexStr: string = ' *(\\d+(?:\\.\\d+)*)'; // Compound number with dot as separator
7+
export const CompoundNumberDashRegexStr: string = ' *(\\d+(?:-\\d+)*)'; // Compound number with dash as separator
148

159
export const DOT_SEPARATOR = '.'
1610
export const DASH_SEPARATOR = '-'
@@ -20,6 +14,15 @@ const PIPE_SEPARATOR = '|' // ASCII 124
2014

2115
export const DEFAULT_NORMALIZATION_PLACES = 8; // Fixed width of a normalized number (with leading zeros)
2216

17+
// Property escapes:
18+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
19+
// https://stackoverflow.com/a/48902765
20+
//
21+
// Using Unicode property escapes to express 'a letter in any modern language'
22+
export const WordInAnyLanguageRegexStr = '(\\p{Letter}+)' // remember about the /u option -> /\p{Letter}+/u
23+
24+
export const WordInASCIIRegexStr = '([a-zA-Z]+)'
25+
2326
export function prependWithZeros(s: string, minLength: number) {
2427
if (s.length < minLength) {
2528
const delta: number = minLength - s.length;

src/custom-sort/sorting-spec-processor.spec.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import {
55
ConsumedFolderMatchingRegexp,
66
consumeFolderByRegexpExpression,
77
convertPlainStringToRegex,
8-
detectNumericSortingSymbols,
8+
detectSortingSymbols,
99
escapeRegexUnsafeCharacters,
10-
extractNumericSortingSymbol,
11-
hasMoreThanOneNumericSortingSymbol,
10+
extractSortingSymbol,
11+
hasMoreThanOneSortingSymbol,
1212
NumberNormalizerFn,
1313
RegexpUsedAs,
1414
RomanNumberNormalizerFn,
1515
SortingSpecProcessor
1616
} from "./sorting-spec-processor"
17-
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec} from "./custom-sort-types";
17+
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec, IdentityNormalizerFn} from "./custom-sort-types";
1818
import {FolderMatchingRegexp, FolderMatchingTreeNode} from "./folder-matching-rules";
1919

2020
const txtInputExampleA: string = `
@@ -347,7 +347,7 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
347347
}
348348
}
349349

350-
const expectedSortSpecsExampleNumericSortingSymbols: { [key: string]: CustomSortSpec } = {
350+
const expectedSortSpecsExampleSortingSymbols: { [key: string]: CustomSortSpec } = {
351351
"mock-folder": {
352352
groups: [{
353353
foldersOnly: true,
@@ -388,21 +388,37 @@ const expectedSortSpecsExampleNumericSortingSymbols: { [key: string]: CustomSort
388388
regex: / *(\d+)plain syntax\?\?\?$/i,
389389
normalizerFn: NumberNormalizerFn
390390
}
391+
}, {
392+
order: CustomSortOrder.alphabetical,
393+
type: CustomSortGroupType.ExactName,
394+
regexPrefix: {
395+
regex: /^Here goes ASCII word ([a-zA-Z]+)$/i,
396+
normalizerFn: IdentityNormalizerFn
397+
}
398+
}, {
399+
order: CustomSortOrder.alphabetical,
400+
type: CustomSortGroupType.ExactName,
401+
regexPrefix: {
402+
regex: /^(\p{Letter}+)\. is for any modern language word$/iu,
403+
normalizerFn: IdentityNormalizerFn
404+
}
391405
}, {
392406
type: CustomSortGroupType.Outsiders,
393407
order: CustomSortOrder.alphabetical,
394408
}],
395409
targetFoldersPaths: ['mock-folder'],
396-
outsidersGroupIdx: 5
410+
outsidersGroupIdx: 7
397411
}
398412
}
399413

400-
const txtInputExampleNumericSortingSymbols: string = `
414+
const txtInputExampleSortingSymbols: string = `
401415
/folders Chapter \\.d+ ...
402416
/:files ...section \\-r+.
403417
% Appendix \\-d+ (attachments)
404418
Plain syntax\\R+ ... works?
405419
And this kind of... \\D+plain syntax???
420+
Here goes ASCII word \\a+
421+
\\A+. is for any modern language word
406422
`
407423

408424
describe('SortingSpecProcessor', () => {
@@ -420,10 +436,10 @@ describe('SortingSpecProcessor', () => {
420436
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
421437
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsExampleA)
422438
})
423-
it('should generate correct SortSpecs (example with numerical sorting symbols)', () => {
424-
const inputTxtArr: Array<string> = txtInputExampleNumericSortingSymbols.split('\n')
439+
it('should generate correct SortSpecs (example with sorting symbols)', () => {
440+
const inputTxtArr: Array<string> = txtInputExampleSortingSymbols.split('\n')
425441
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
426-
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsExampleNumericSortingSymbols)
442+
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsExampleSortingSymbols)
427443
})
428444
})
429445

@@ -1735,7 +1751,7 @@ describe('SortingSpecProcessor error detection and reporting', () => {
17351751
expect(result).toBeNull()
17361752
expect(errorsLogger).toHaveBeenCalledTimes(2)
17371753
expect(errorsLogger).toHaveBeenNthCalledWith(1,
1738-
`${ERR_PREFIX} 9:TooManyNumericSortingSymbols Maximum one numeric sorting indicator allowed per line ${ERR_SUFFIX_IN_LINE(2)}`)
1754+
`${ERR_PREFIX} 9:TooManySortingSymbols Maximum one sorting symbol allowed per line ${ERR_SUFFIX_IN_LINE(2)}`)
17391755
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('% Chapter\\R+ ... page\\d+ '))
17401756
})
17411757
it('should recognize error: nested standard obsidian sorting attribute', () => {
@@ -1916,7 +1932,7 @@ describe('SortingSpecProcessor error detection and reporting', () => {
19161932
expect(result).toBeNull()
19171933
expect(errorsLogger).toHaveBeenCalledTimes(2)
19181934
expect(errorsLogger).toHaveBeenNthCalledWith(1,
1919-
`${ERR_PREFIX} 10:NumericalSymbolAdjacentToWildcard Numerical sorting symbol must not be directly adjacent to a wildcard because of potential performance problem. An additional explicit separator helps in such case. ${ERR_SUFFIX_IN_LINE(1)}`)
1935+
`${ERR_PREFIX} 10:SortingSymbolAdjacentToWildcard Sorting symbol must not be directly adjacent to a wildcard because of potential performance problem. An additional explicit separator helps in such case. ${ERR_SUFFIX_IN_LINE(1)}`)
19201936
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT(s))
19211937
})
19221938
it.each([
@@ -2092,7 +2108,7 @@ describe('escapeRegexUnsafeCharacters', () => {
20922108
})
20932109
})
20942110

2095-
describe('detectNumericSortingSymbols', () => {
2111+
describe('detectSortingSymbols', () => {
20962112
it.each([
20972113
['', false],
20982114
['d+', false],
@@ -2107,12 +2123,12 @@ describe('detectNumericSortingSymbols', () => {
21072123
['\\d+abcd\\d+efgh', true],
21082124
['\\d+\\.D+\\-d+\\R+\\.r+\\-R+ \\d+', true]
21092125
])('should correctly detect in >%s< (%s) sorting regex symbols', (s: string, b: boolean) => {
2110-
const result = detectNumericSortingSymbols(s)
2126+
const result = detectSortingSymbols(s)
21112127
expect(result).toBe(b)
21122128
})
21132129
})
21142130

2115-
describe('hasMoreThanOneNumericSortingSymbol', () => {
2131+
describe('hasMoreThanOneSortingSymbol', () => {
21162132
it.each([
21172133
['', false],
21182134
[' d+', false],
@@ -2128,12 +2144,12 @@ describe('hasMoreThanOneNumericSortingSymbol', () => {
21282144
['\\R+abcd\\.R+efgh', true],
21292145
['\\d+\\.D+\\-d+\\R+\\.r+\\-R+ \\d+', true]
21302146
])('should correctly detect in >%s< (%s) sorting regex symbols', (s: string, b: boolean) => {
2131-
const result = hasMoreThanOneNumericSortingSymbol(s)
2147+
const result = hasMoreThanOneSortingSymbol(s)
21322148
expect(result).toBe(b)
21332149
})
21342150
})
21352151

2136-
describe('extractNumericSortingSymbol', () => {
2152+
describe('extractSortingSymbol', () => {
21372153
it.each([
21382154
['', null],
21392155
['d+', null],
@@ -2144,7 +2160,7 @@ describe('extractNumericSortingSymbol', () => {
21442160
['--\\.D+\\d+', '\\.D+'],
21452161
['wdwqwqe\\d+\\.D+\\-d+\\R+\\.r+\\-R+ \\d+', '\\d+']
21462162
])('should correctly extract from >%s< the numeric sorting symbol (%s)', (s: string, ss: string) => {
2147-
const result = extractNumericSortingSymbol(s)
2163+
const result = extractSortingSymbol(s)
21482164
expect(result).toBe(ss)
21492165
})
21502166
})

0 commit comments

Comments
 (0)