Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianMC committed Mar 7, 2023
2 parents c94b8d9 + 68ef5f1 commit 6298c08
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 75 deletions.
1 change: 1 addition & 0 deletions src/custom-sort/custom-sort-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface RecognizedOrderValue {
}

export type NormalizerFn = (s: string) => string | null
export const IdentityNormalizerFn: NormalizerFn = (s: string) => s

export interface RegExpSpec {
regex: RegExp
Expand Down
109 changes: 97 additions & 12 deletions src/custom-sort/matchers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import {
getNormalizedRomanNumber,
prependWithZeros,
romanToIntStr,
NumberRegex,
CompoundNumberDotRegex,
CompoundNumberDashRegex,
RomanNumberRegex,
CompoundRomanNumberDotRegex,
CompoundRomanNumberDashRegex
NumberRegexStr,
CompoundNumberDotRegexStr,
CompoundNumberDashRegexStr,
RomanNumberRegexStr,
CompoundRomanNumberDotRegexStr,
CompoundRomanNumberDashRegexStr,
WordInASCIIRegexStr,
WordInAnyLanguageRegexStr
} from "./matchers";
import {SortingSpecProcessor} from "./sorting-spec-processor";

describe('Plain numbers regexp', () => {
let regexp: RegExp;
beforeEach(() => {
regexp = new RegExp('^' + NumberRegexStr, 'i');
});
it.each([
['', null],
[' ', null],
Expand All @@ -23,7 +30,7 @@ describe('Plain numbers regexp', () => {
['9', '9'],
['7328964783268794325496783', '7328964783268794325496783']
])('%s => %s', (s: string, out: string | null) => {
const match: RegExpMatchArray | null = s.match(NumberRegex)
const match: RegExpMatchArray | null = s.match(regexp)
if (out) {
expect(match).not.toBeNull()
expect(match?.[1]).toBe(out)
Expand All @@ -34,6 +41,10 @@ describe('Plain numbers regexp', () => {
})

describe('Plain compound numbers regexp (dot)', () => {
let regexp: RegExp;
beforeEach(() => {
regexp = new RegExp('^' + CompoundNumberDotRegexStr, 'i');
});
it.each([
['', null],
[' ', null],
Expand All @@ -55,7 +66,7 @@ describe('Plain compound numbers regexp (dot)', () => {
['56.78.-.1abc', '56.78'],
['56.78-.1abc', '56.78'],
])('%s => %s', (s: string, out: string | null) => {
const match: RegExpMatchArray | null = s.match(CompoundNumberDotRegex)
const match: RegExpMatchArray | null = s.match(regexp)
if (out) {
expect(match).not.toBeNull()
expect(match?.[1]).toBe(out)
Expand All @@ -66,6 +77,10 @@ describe('Plain compound numbers regexp (dot)', () => {
})

describe('Plain compound numbers regexp (dash)', () => {
let regexp: RegExp;
beforeEach(() => {
regexp = new RegExp('^' + CompoundNumberDashRegexStr, 'i');
});
it.each([
['', null],
[' ', null],
Expand All @@ -87,7 +102,7 @@ describe('Plain compound numbers regexp (dash)', () => {
['56-78-.-1abc', '56-78'],
['56-78.-1abc', '56-78'],
])('%s => %s', (s: string, out: string | null) => {
const match: RegExpMatchArray | null = s.match(CompoundNumberDashRegex)
const match: RegExpMatchArray | null = s.match(regexp)
if (out) {
expect(match).not.toBeNull()
expect(match?.[1]).toBe(out)
Expand All @@ -98,6 +113,10 @@ describe('Plain compound numbers regexp (dash)', () => {
})

describe('Plain Roman numbers regexp', () => {
let regexp: RegExp;
beforeEach(() => {
regexp = new RegExp('^' + RomanNumberRegexStr, 'i');
});
it.each([
['', null],
[' ', null],
Expand All @@ -109,7 +128,7 @@ describe('Plain Roman numbers regexp', () => {
['iiiii', 'iiiii'],
['viviviv794325496783', 'viviviv']
])('%s => %s', (s: string, out: string | null) => {
const match: RegExpMatchArray | null = s.match(RomanNumberRegex)
const match: RegExpMatchArray | null = s.match(regexp)
if (out) {
expect(match).not.toBeNull()
expect(match?.[1]).toBe(out)
Expand All @@ -120,6 +139,10 @@ describe('Plain Roman numbers regexp', () => {
})

describe('Roman compound numbers regexp (dot)', () => {
let regexp: RegExp;
beforeEach(() => {
regexp = new RegExp('^' + CompoundRomanNumberDotRegexStr, 'i');
});
it.each([
['', null],
[' ', null],
Expand All @@ -143,7 +166,7 @@ describe('Roman compound numbers regexp (dot)', () => {
['xvx.d-.iabc', 'xvx.d'],
['xvx.d..iabc', 'xvx.d'],
])('%s => %s', (s: string, out: string | null) => {
const match: RegExpMatchArray | null = s.match(CompoundRomanNumberDotRegex)
const match: RegExpMatchArray | null = s.match(regexp)
if (out) {
expect(match).not.toBeNull()
expect(match?.[1]).toBe(out)
Expand All @@ -154,6 +177,10 @@ describe('Roman compound numbers regexp (dot)', () => {
})

describe('Roman compound numbers regexp (dash)', () => {
let regexp: RegExp;
beforeEach(() => {
regexp = new RegExp('^' + CompoundRomanNumberDashRegexStr, 'i');
});
it.each([
['', null],
[' ', null],
Expand All @@ -177,7 +204,65 @@ describe('Roman compound numbers regexp (dash)', () => {
['xvx-d.-iabc', 'xvx-d'],
['xvx-d--iabc', 'xvx-d']
])('%s => %s', (s: string, out: string | null) => {
const match: RegExpMatchArray | null = s.match(CompoundRomanNumberDashRegex)
const match: RegExpMatchArray | null = s.match(regexp)
if (out) {
expect(match).not.toBeNull()
expect(match?.[1]).toBe(out)
} else {
expect(match).toBeNull()
}
})
})

describe('ASCII word regexp', () => {
let regexp: RegExp;
beforeEach(() => {
regexp = new RegExp('^' + WordInASCIIRegexStr, 'i');
});
it.each([
['', null],
[' ', null],
[' I', null], // leading spaces are not swallowed
['I ', 'I'], // trailing spaces are swallowed
['Abc', 'Abc'],
['Sun', 'Sun'],
['Hello123', 'Hello'],
['John_', 'John'],
['Title.', 'Title'],
['Deutschstäder', 'Deutschst'],
['ItalianoàèéìòùÈ', 'Italiano'],
['PolskićśńĄł', 'Polski']
])('%s => %s', (s: string, out: string | null) => {
const match: RegExpMatchArray | null = s.match(regexp)
if (out) {
expect(match).not.toBeNull()
expect(match?.[1]).toBe(out)
} else {
expect(match).toBeNull()
}
})
})

describe('Unicode word regexp', () => {
let regexp: RegExp;
beforeEach(() => {
regexp = new RegExp('^' + WordInAnyLanguageRegexStr, 'ui');
});
it.each([
['', null],
[' ', null],
[' I', null], // leading spaces are not swallowed
['I ', 'I'], // trailing characters are ignored in unit test
['Abc', 'Abc'],
['Sun', 'Sun'],
['Hello123', 'Hello'],
['John_', 'John'],
['Title.', 'Title'],
['Deutschstäder_', 'Deutschstäder'],
['ItalianoàèéìòùÈ', 'ItalianoàèéìòùÈ'],
['PolskićśńĄł', 'PolskićśńĄł']
])('%s => %s', (s: string, out: string | null) => {
const match: RegExpMatchArray | null = s.match(regexp)
if (out) {
expect(match).not.toBeNull()
expect(match?.[1]).toBe(out)
Expand Down
27 changes: 15 additions & 12 deletions src/custom-sort/matchers.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
export const RomanNumberRegex: RegExp = /^ *([MDCLXVI]+)/i; // Roman number
export const RomanNumberRegexStr: string = ' *([MDCLXVI]+)';
export const CompoundRomanNumberDotRegex: RegExp = /^ *([MDCLXVI]+(?:\.[MDCLXVI]+)*)/i; // Compound Roman number with dot as separator
export const CompoundRomanNumberDotRegexStr: string = ' *([MDCLXVI]+(?:\\.[MDCLXVI]+)*)';
export const CompoundRomanNumberDashRegex: RegExp = /^ *([MDCLXVI]+(?:-[MDCLXVI]+)*)/i; // Compound Roman number with dash as separator
export const CompoundRomanNumberDashRegexStr: string = ' *([MDCLXVI]+(?:-[MDCLXVI]+)*)';
export const RomanNumberRegexStr: string = ' *([MDCLXVI]+)'; // Roman number
export const CompoundRomanNumberDotRegexStr: string = ' *([MDCLXVI]+(?:\\.[MDCLXVI]+)*)';// Compound Roman number with dot as separator
export const CompoundRomanNumberDashRegexStr: string = ' *([MDCLXVI]+(?:-[MDCLXVI]+)*)'; // Compound Roman number with dash as separator

export const NumberRegex: RegExp = /^ *(\d+)/; // Plain number
export const NumberRegexStr: string = ' *(\\d+)';
export const CompoundNumberDotRegex: RegExp = /^ *(\d+(?:\.\d+)*)/; // Compound number with dot as separator
export const CompoundNumberDotRegexStr: string = ' *(\\d+(?:\\.\\d+)*)';
export const CompoundNumberDashRegex: RegExp = /^ *(\d+(?:-\d+)*)/; // Compound number with dash as separator
export const CompoundNumberDashRegexStr: string = ' *(\\d+(?:-\\d+)*)';
export const NumberRegexStr: string = ' *(\\d+)'; // Plain number
export const CompoundNumberDotRegexStr: string = ' *(\\d+(?:\\.\\d+)*)'; // Compound number with dot as separator
export const CompoundNumberDashRegexStr: string = ' *(\\d+(?:-\\d+)*)'; // Compound number with dash as separator

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

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

// Property escapes:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes
// https://stackoverflow.com/a/48902765
//
// Using Unicode property escapes to express 'a letter in any modern language'
export const WordInAnyLanguageRegexStr = '(\\p{Letter}+)' // remember about the /u option -> /\p{Letter}+/u

export const WordInASCIIRegexStr = '([a-zA-Z]+)'

export function prependWithZeros(s: string, minLength: number) {
if (s.length < minLength) {
const delta: number = minLength - s.length;
Expand Down
52 changes: 34 additions & 18 deletions src/custom-sort/sorting-spec-processor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import {
ConsumedFolderMatchingRegexp,
consumeFolderByRegexpExpression,
convertPlainStringToRegex,
detectNumericSortingSymbols,
detectSortingSymbols,
escapeRegexUnsafeCharacters,
extractNumericSortingSymbol,
hasMoreThanOneNumericSortingSymbol,
extractSortingSymbol,
hasMoreThanOneSortingSymbol,
NumberNormalizerFn,
RegexpUsedAs,
RomanNumberNormalizerFn,
SortingSpecProcessor
} from "./sorting-spec-processor"
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec} from "./custom-sort-types";
import {CustomSortGroupType, CustomSortOrder, CustomSortSpec, IdentityNormalizerFn} from "./custom-sort-types";
import {FolderMatchingRegexp, FolderMatchingTreeNode} from "./folder-matching-rules";

const txtInputExampleA: string = `
Expand Down Expand Up @@ -347,7 +347,7 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
}
}

const expectedSortSpecsExampleNumericSortingSymbols: { [key: string]: CustomSortSpec } = {
const expectedSortSpecsExampleSortingSymbols: { [key: string]: CustomSortSpec } = {
"mock-folder": {
groups: [{
foldersOnly: true,
Expand Down Expand Up @@ -388,21 +388,37 @@ const expectedSortSpecsExampleNumericSortingSymbols: { [key: string]: CustomSort
regex: / *(\d+)plain syntax\?\?\?$/i,
normalizerFn: NumberNormalizerFn
}
}, {
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.ExactName,
regexPrefix: {
regex: /^Here goes ASCII word ([a-zA-Z]+)$/i,
normalizerFn: IdentityNormalizerFn
}
}, {
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.ExactName,
regexPrefix: {
regex: /^(\p{Letter}+)\. is for any modern language word$/iu,
normalizerFn: IdentityNormalizerFn
}
}, {
type: CustomSortGroupType.Outsiders,
order: CustomSortOrder.alphabetical,
}],
targetFoldersPaths: ['mock-folder'],
outsidersGroupIdx: 5
outsidersGroupIdx: 7
}
}

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

describe('SortingSpecProcessor', () => {
Expand All @@ -420,10 +436,10 @@ describe('SortingSpecProcessor', () => {
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsExampleA)
})
it('should generate correct SortSpecs (example with numerical sorting symbols)', () => {
const inputTxtArr: Array<string> = txtInputExampleNumericSortingSymbols.split('\n')
it('should generate correct SortSpecs (example with sorting symbols)', () => {
const inputTxtArr: Array<string> = txtInputExampleSortingSymbols.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsExampleNumericSortingSymbols)
expect(result?.sortSpecByPath).toEqual(expectedSortSpecsExampleSortingSymbols)
})
})

Expand Down Expand Up @@ -1735,7 +1751,7 @@ describe('SortingSpecProcessor error detection and reporting', () => {
expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(2)
expect(errorsLogger).toHaveBeenNthCalledWith(1,
`${ERR_PREFIX} 9:TooManyNumericSortingSymbols Maximum one numeric sorting indicator allowed per line ${ERR_SUFFIX_IN_LINE(2)}`)
`${ERR_PREFIX} 9:TooManySortingSymbols Maximum one sorting symbol allowed per line ${ERR_SUFFIX_IN_LINE(2)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('% Chapter\\R+ ... page\\d+ '))
})
it('should recognize error: nested standard obsidian sorting attribute', () => {
Expand Down Expand Up @@ -1916,7 +1932,7 @@ describe('SortingSpecProcessor error detection and reporting', () => {
expect(result).toBeNull()
expect(errorsLogger).toHaveBeenCalledTimes(2)
expect(errorsLogger).toHaveBeenNthCalledWith(1,
`${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)}`)
`${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)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT(s))
})
it.each([
Expand Down Expand Up @@ -2092,7 +2108,7 @@ describe('escapeRegexUnsafeCharacters', () => {
})
})

describe('detectNumericSortingSymbols', () => {
describe('detectSortingSymbols', () => {
it.each([
['', false],
['d+', false],
Expand All @@ -2107,12 +2123,12 @@ describe('detectNumericSortingSymbols', () => {
['\\d+abcd\\d+efgh', true],
['\\d+\\.D+\\-d+\\R+\\.r+\\-R+ \\d+', true]
])('should correctly detect in >%s< (%s) sorting regex symbols', (s: string, b: boolean) => {
const result = detectNumericSortingSymbols(s)
const result = detectSortingSymbols(s)
expect(result).toBe(b)
})
})

describe('hasMoreThanOneNumericSortingSymbol', () => {
describe('hasMoreThanOneSortingSymbol', () => {
it.each([
['', false],
[' d+', false],
Expand All @@ -2128,12 +2144,12 @@ describe('hasMoreThanOneNumericSortingSymbol', () => {
['\\R+abcd\\.R+efgh', true],
['\\d+\\.D+\\-d+\\R+\\.r+\\-R+ \\d+', true]
])('should correctly detect in >%s< (%s) sorting regex symbols', (s: string, b: boolean) => {
const result = hasMoreThanOneNumericSortingSymbol(s)
const result = hasMoreThanOneSortingSymbol(s)
expect(result).toBe(b)
})
})

describe('extractNumericSortingSymbol', () => {
describe('extractSortingSymbol', () => {
it.each([
['', null],
['d+', null],
Expand All @@ -2144,7 +2160,7 @@ describe('extractNumericSortingSymbol', () => {
['--\\.D+\\d+', '\\.D+'],
['wdwqwqe\\d+\\.D+\\-d+\\R+\\.r+\\-R+ \\d+', '\\d+']
])('should correctly extract from >%s< the numeric sorting symbol (%s)', (s: string, ss: string) => {
const result = extractNumericSortingSymbol(s)
const result = extractSortingSymbol(s)
expect(result).toBe(ss)
})
})
Expand Down
Loading

0 comments on commit 6298c08

Please sign in to comment.