Support for implicit sorting specs - refined priorities of implicit vs explicit rules

- new unit tests cover the changes
This commit is contained in:
SebastianMC 2023-09-18 14:04:33 +02:00
parent 76ee3dfa62
commit 24af493734
3 changed files with 187 additions and 1 deletions

View File

@ -3,6 +3,7 @@ import {FolderWildcardMatching} from './folder-matching-rules'
type SortingSpec = string type SortingSpec = string
const checkIfImplicitSpec = (s: SortingSpec) => false const checkIfImplicitSpec = (s: SortingSpec) => false
const checkIfImplicitSpecByPrefix = (s: SortingSpec) => s.startsWith('implicit:')
const createMockMatcherRichVersion = (): FolderWildcardMatching<SortingSpec> => { const createMockMatcherRichVersion = (): FolderWildcardMatching<SortingSpec> => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec) const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
@ -116,6 +117,13 @@ describe('folderMatch', () => {
expect(result).toEqual({errorMsg: "Duplicate wildcard '...' specification for /Archive/2020/.../"}) expect(result).toEqual({errorMsg: "Duplicate wildcard '...' specification for /Archive/2020/.../"})
}) })
it('should accept duplicate match children definitions for same path, if the former comes from implicit spec', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpecByPrefix)
matcher.addWildcardDefinition('Archive/2020/...', 'implicit: First occurrence')
const result = matcher.addWildcardDefinition('/Archive/2020/.../', 'Duplicate')
expect(result).toBeUndefined()
})
it('should detect duplicate match all definitions for same path', () => { it('should detect duplicate match all definitions for same path', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec) const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addWildcardDefinition('/Archive/2019/*', 'First occurrence') matcher.addWildcardDefinition('/Archive/2019/*', 'First occurrence')
@ -123,6 +131,13 @@ describe('folderMatch', () => {
expect(result).toEqual({errorMsg: "Duplicate wildcard '*' specification for Archive/2019/*"}) expect(result).toEqual({errorMsg: "Duplicate wildcard '*' specification for Archive/2019/*"})
}) })
it('should accept duplicate match all definitions for same path, if the former comes from implicit spec', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpecByPrefix)
matcher.addWildcardDefinition('/Archive/2019/*', 'implicit: First occurrence')
const result = matcher.addWildcardDefinition('Archive/2019/*', 'Duplicate')
expect(result).toBeUndefined()
})
it('regexp-match by name works (order of regexp doesn\'t matter) case A', () => { it('regexp-match by name works (order of regexp doesn\'t matter) case A', () => {
const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec) const matcher: FolderWildcardMatching<SortingSpec> = new FolderWildcardMatching(checkIfImplicitSpec)
matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`) matcher.addRegexpDefinition(/^daily$/, false, undefined, false, `r1`)

View File

@ -1415,6 +1415,13 @@ const txtInputTargetFolderMultiSpecD: string = `
target-folder: ./* target-folder: ./*
` `
const txtInputTargetFolderMultiSpecD_implicitCase: string = `
// typically implicit specs come context-free, no notion of current folder, that's why using explicit target-folder path
target-folder: mock-folder/*
// Reverse order to distinguish between implicit and explicit spec
> a-z
`
const expectedSortSpecForMultiSpecD: { [key: string]: CustomSortSpec } = { const expectedSortSpecForMultiSpecD: { [key: string]: CustomSortSpec } = {
'mock-folder': { 'mock-folder': {
groups: [{ groups: [{
@ -1426,6 +1433,19 @@ const expectedSortSpecForMultiSpecD: { [key: string]: CustomSortSpec } = {
} }
} }
const expectedSortSpecForMultiSpecD_implicitCase: { [key: string]: CustomSortSpec } = {
'mock-folder': {
defaultOrder: CustomSortOrder.alphabeticalReverse,
groups: [{
order: CustomSortOrder.alphabeticalReverse,
type: CustomSortGroupType.Outsiders
}],
outsidersGroupIdx: 0,
targetFoldersPaths: ['mock-folder/*'],
implicit: true
}
}
const expectedWildcardMatchingTreeForMultiSpecD: FolderMatchingTreeNode<CustomSortSpec> = { const expectedWildcardMatchingTreeForMultiSpecD: FolderMatchingTreeNode<CustomSortSpec> = {
subtree: { subtree: {
"mock-folder": { "mock-folder": {
@ -1443,10 +1463,36 @@ const expectedWildcardMatchingTreeForMultiSpecD: FolderMatchingTreeNode<CustomSo
} }
} }
const expectedWildcardMatchingTreeForMultiSpecD_implicitCase: FolderMatchingTreeNode<CustomSortSpec> = {
subtree: {
"mock-folder": {
matchAll: {
defaultOrder: CustomSortOrder.alphabeticalReverse,
"groups": [{
"order": CustomSortOrder.alphabeticalReverse,
"type": CustomSortGroupType.Outsiders
}],
"outsidersGroupIdx": 0,
"targetFoldersPaths": ["mock-folder/*"],
implicit: true
},
name: "mock-folder",
subtree: {}
}
}
}
const txtInputTargetFolderMultiSpecE: string = ` const txtInputTargetFolderMultiSpecE: string = `
target-folder: mock-folder/... target-folder: mock-folder/...
` `
const txtInputTargetFolderMultiSpecE_implicitCase: string = `
// typically implicit specs come context-free, no notion of current folder, that's why using explicit target-folder path
target-folder: mock-folder/...
// Reverse order to distinguish between implicit and explicit spec
> a-z
`
const expectedSortSpecForMultiSpecE: { [key: string]: CustomSortSpec } = { const expectedSortSpecForMultiSpecE: { [key: string]: CustomSortSpec } = {
'mock-folder': { 'mock-folder': {
groups: [{ groups: [{
@ -1458,6 +1504,19 @@ const expectedSortSpecForMultiSpecE: { [key: string]: CustomSortSpec } = {
} }
} }
const expectedSortSpecForMultiSpecE_implicitCase: { [key: string]: CustomSortSpec } = {
'mock-folder': {
defaultOrder: CustomSortOrder.alphabeticalReverse,
groups: [{
order: CustomSortOrder.alphabeticalReverse,
type: CustomSortGroupType.Outsiders
}],
outsidersGroupIdx: 0,
targetFoldersPaths: ['mock-folder/...'],
implicit: true
}
}
const expectedWildcardMatchingTreeForMultiSpecE: FolderMatchingTreeNode<CustomSortSpec> = { const expectedWildcardMatchingTreeForMultiSpecE: FolderMatchingTreeNode<CustomSortSpec> = {
subtree: { subtree: {
"mock-folder": { "mock-folder": {
@ -1475,6 +1534,72 @@ const expectedWildcardMatchingTreeForMultiSpecE: FolderMatchingTreeNode<CustomSo
} }
} }
const expectedWildcardMatchingTreeForMultiSpecE_implicitCase: FolderMatchingTreeNode<CustomSortSpec> = {
subtree: {
"mock-folder": {
matchChildren: {
defaultOrder: CustomSortOrder.alphabeticalReverse,
"groups": [{
"order": CustomSortOrder.alphabeticalReverse,
"type": CustomSortGroupType.Outsiders
}],
"outsidersGroupIdx": 0,
"targetFoldersPaths": ["mock-folder/..."],
implicit: true
},
name: "mock-folder",
subtree: {}
}
}
}
const expectedWildcardMatchingTreeForMultiSpecDplusE_implicitCase: FolderMatchingTreeNode<CustomSortSpec> = {
subtree: {
"mock-folder": {
matchAll: {
"groups": [{
"order": CustomSortOrder.alphabetical,
"type": CustomSortGroupType.Outsiders
}],
"outsidersGroupIdx": 0,
"targetFoldersPaths": ["mock-folder/*"]
},
matchChildren: {
defaultOrder: CustomSortOrder.alphabeticalReverse,
"groups": [{
"order": CustomSortOrder.alphabeticalReverse,
"type": CustomSortGroupType.Outsiders
}],
"outsidersGroupIdx": 0,
"targetFoldersPaths": ["mock-folder/..."],
implicit: true
},
name: "mock-folder",
subtree: {}
}
}
}
const txtInputTargetFolderMultiSpecF_implicitCase: string = `
// typically implicit specs come context-free, no notion of current folder, that's why using explicit target-folder path
target-folder: mock-folder
// Reverse order to distinguish between implicit and explicit spec
> a-z
`
const expectedSortSpecForMultiSpecF_implicitCase: { [key: string]: CustomSortSpec } = {
'mock-folder': {
defaultOrder: CustomSortOrder.alphabeticalReverse,
groups: [{
order: CustomSortOrder.alphabeticalReverse,
type: CustomSortGroupType.Outsiders
}],
outsidersGroupIdx: 0,
targetFoldersPaths: ['mock-folder'],
implicit: true
}
}
describe('SortingSpecProcessor path wildcard priorities', () => { describe('SortingSpecProcessor path wildcard priorities', () => {
let processor: SortingSpecProcessor; let processor: SortingSpecProcessor;
beforeEach(() => { beforeEach(() => {
@ -1504,12 +1629,52 @@ describe('SortingSpecProcessor path wildcard priorities', () => {
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecD) expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecD)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecD) expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecD)
}) })
it('should not raise error for multiple spec for the same path and choose correct spec, case D - with implicit spec', () => {
const inputTxtArrImplicit: Array<string> = txtInputTargetFolderMultiSpecD_implicitCase.split('\n')
const resultImplicit = processor.parseSortSpecFromText(inputTxtArrImplicit, 'implicit ignored param', 'implicit ignored param', null, true)
expect(resultImplicit?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecD_implicitCase)
expect(resultImplicit?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecD_implicitCase)
const inputTxtArrExplicit: Array<string> = txtInputTargetFolderMultiSpecD.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArrExplicit, 'mock-folder', 'custom-name-note.md', resultImplicit)
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecD)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecD)
})
it('should not raise error for multiple spec for the same path and choose correct spec, case E', () => { it('should not raise error for multiple spec for the same path and choose correct spec, case E', () => {
const inputTxtArr: Array<string> = txtInputTargetFolderMultiSpecE.split('\n') const inputTxtArr: Array<string> = txtInputTargetFolderMultiSpecE.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md') const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecE) expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecE)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecE) expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecE)
}) })
it('should not raise error for multiple spec for the same path and choose correct spec, case E - with implicit spec', () => {
const inputTxtArrImplicit: Array<string> = txtInputTargetFolderMultiSpecE_implicitCase.split('\n')
const resultImplicit = processor.parseSortSpecFromText(inputTxtArrImplicit, 'implicit ignored param', 'implicit ignored param', null, true)
expect(resultImplicit?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecE_implicitCase)
expect(resultImplicit?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecE_implicitCase)
const inputTxtArrExplicit: Array<string> = txtInputTargetFolderMultiSpecE.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArrExplicit, 'mock-folder', 'custom-name-note.md', resultImplicit)
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecE)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecE)
})
it('should not raise error for multiple spec for the same path and choose correct spec, mixed case D+E - with implicit spec, which looses all', () => {
const inputTxtArrImplicit: Array<string> = txtInputTargetFolderMultiSpecE_implicitCase.split('\n')
const resultImplicit = processor.parseSortSpecFromText(inputTxtArrImplicit, 'implicit ignored param', 'implicit ignored param', null, true)
expect(resultImplicit?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecE_implicitCase)
expect(resultImplicit?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecE_implicitCase)
const inputTxtArrExplicit: Array<string> = txtInputTargetFolderMultiSpecD.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArrExplicit, 'mock-folder', 'custom-name-note.md', resultImplicit)
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecD)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecDplusE_implicitCase)
})
it('should not raise error for multiple spec for the same path and choose correct spec, mixed case E+F => implicit by name', () => {
const inputTxtArrImplicit: Array<string> = txtInputTargetFolderMultiSpecF_implicitCase.split('\n')
const resultImplicit = processor.parseSortSpecFromText(inputTxtArrImplicit, 'implicit ignored param', 'implicit ignored param', null, true)
expect(resultImplicit?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecF_implicitCase)
expect(resultImplicit?.sortSpecByWildcard?.tree).toBeUndefined()
const inputTxtArrExplicit: Array<string> = txtInputTargetFolderMultiSpecE.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArrExplicit, 'mock-folder', 'custom-name-note.md', resultImplicit)
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForMultiSpecF_implicitCase)
expect(result?.sortSpecByWildcard?.tree).toEqual(expectedWildcardMatchingTreeForMultiSpecE)
})
}) })
const txtInputAdvancedFolderDateSortingMethods: string = ` const txtInputAdvancedFolderDateSortingMethods: string = `

View File

@ -601,12 +601,18 @@ const endsWithWildcardPatternSuffix = (path: string): boolean => {
path.endsWith(MATCH_ALL_SUFFIX) path.endsWith(MATCH_ALL_SUFFIX)
} }
/*
Important note:
even if the actual enum labels seem to be unused thus unneeded, their numerical values (implied by the order below) matter.
They define the priorities of the rules for wilcard target-folder specs. The rule with higher priority overrides the one with lower
*/
enum WildcardPriority { enum WildcardPriority {
NO_WILDCARD = 1, NO_WILDCARD = 1,
NO_WILDCARD_IMPLICIT, NO_WILDCARD_IMPLICIT,
MATCH_CHILDREN, MATCH_CHILDREN,
MATCH_CHILDREN_IMPLICIT,
MATCH_ALL, MATCH_ALL,
MATCH_CHILDREN_IMPLICIT,
MATCH_ALL_IMPLICIT MATCH_ALL_IMPLICIT
} }