- full unit tests coverage of the new functionality - refactor of the parser to allow more flexible syntax and be able to detect more errors - introduced many new errors recognized by the parser
This commit is contained in:
parent
581f5e9f36
commit
84a5238814
|
@ -30,7 +30,7 @@ sorting-spec: |
|
||||||
|
|
||||||
The resulting order of notes would be:
|
The resulting order of notes would be:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
However, a group can be assigned a higher priority in the sorting spec. In result, folder items will be matched against them _before_ any other rules. To impose a priority on a group use the prefix `/!` or `/!!` or `/!!!`
|
However, a group can be assigned a higher priority in the sorting spec. In result, folder items will be matched against them _before_ any other rules. To impose a priority on a group use the prefix `/!` or `/!!` or `/!!!`
|
||||||
|
|
||||||
|
|
|
@ -51,13 +51,14 @@ export interface CustomSortGroup {
|
||||||
exactPrefix?: string
|
exactPrefix?: string
|
||||||
exactSuffix?: string
|
exactSuffix?: string
|
||||||
order?: CustomSortOrder
|
order?: CustomSortOrder
|
||||||
byMetadataField?: string // for 'by-metadata:' if the order is by metadata alphabetical or reverse
|
byMetadataField?: string // for 'by-metadata:' sorting if the order is by metadata alphabetical or reverse
|
||||||
secondaryOrder?: CustomSortOrder
|
secondaryOrder?: CustomSortOrder
|
||||||
filesOnly?: boolean
|
filesOnly?: boolean
|
||||||
matchFilenameWithExt?: boolean
|
matchFilenameWithExt?: boolean
|
||||||
foldersOnly?: boolean
|
foldersOnly?: boolean
|
||||||
withMetadataFieldName?: string // for 'with-metadata:'
|
withMetadataFieldName?: string // for 'with-metadata:' grouping
|
||||||
priority?: number
|
priority?: number
|
||||||
|
combineWithIdx?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomSortSpec {
|
export interface CustomSortSpec {
|
||||||
|
|
|
@ -834,6 +834,111 @@ describe('determineSortingGroup', () => {
|
||||||
path: 'Some parent folder/Abcdef!.md'
|
path: 'Some parent folder/Abcdef!.md'
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
it('should correctly recognize and apply combined group', () => {
|
||||||
|
// given
|
||||||
|
const file1: TFile = mockTFile('Hello :-) ha', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||||
|
const file2: TFile = mockTFile('Hello World :-)', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
groups: [{
|
||||||
|
exactSuffix: "def!",
|
||||||
|
order: CustomSortOrder.alphabeticalReverse,
|
||||||
|
type: CustomSortGroupType.ExactSuffix
|
||||||
|
}, {
|
||||||
|
exactPrefix: "Hello :-)",
|
||||||
|
order: CustomSortOrder.alphabeticalReverse,
|
||||||
|
type: CustomSortGroupType.ExactPrefix,
|
||||||
|
combineWithIdx: 1
|
||||||
|
}, {
|
||||||
|
exactText: "Hello World :-)",
|
||||||
|
order: CustomSortOrder.alphabeticalReverse,
|
||||||
|
type: CustomSortGroupType.ExactName,
|
||||||
|
combineWithIdx: 1
|
||||||
|
}, {
|
||||||
|
filesOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.MatchAll
|
||||||
|
}, {
|
||||||
|
foldersOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.MatchAll
|
||||||
|
}, {
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}],
|
||||||
|
outsidersGroupIdx: 5,
|
||||||
|
targetFoldersPaths: ['/']
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result1 = determineSortingGroup(file1, sortSpec)
|
||||||
|
const result2 = determineSortingGroup(file2, sortSpec)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result1).toEqual({
|
||||||
|
groupIdx: 1, // Imposed by combined groups
|
||||||
|
isFolder: false,
|
||||||
|
sortString: "Hello :-) ha.md",
|
||||||
|
ctimeNewest: MOCK_TIMESTAMP + 222,
|
||||||
|
ctimeOldest: MOCK_TIMESTAMP + 222,
|
||||||
|
mtime: MOCK_TIMESTAMP + 333,
|
||||||
|
path: 'Some parent folder/Hello :-) ha.md'
|
||||||
|
});
|
||||||
|
expect(result2).toEqual({
|
||||||
|
groupIdx: 1, // Imposed by combined groups
|
||||||
|
isFolder: false,
|
||||||
|
sortString: "Hello World :-).md",
|
||||||
|
ctimeNewest: MOCK_TIMESTAMP + 222,
|
||||||
|
ctimeOldest: MOCK_TIMESTAMP + 222,
|
||||||
|
mtime: MOCK_TIMESTAMP + 333,
|
||||||
|
path: 'Some parent folder/Hello World :-).md'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
it('should correctly recognize and apply combined group in connection with priorities', () => {
|
||||||
|
// given
|
||||||
|
const file: TFile = mockTFile('Hello :-)', 'md', 111, MOCK_TIMESTAMP + 222, MOCK_TIMESTAMP + 333);
|
||||||
|
const sortSpec: CustomSortSpec = {
|
||||||
|
groups: [{
|
||||||
|
filesOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.MatchAll
|
||||||
|
}, {
|
||||||
|
foldersOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.MatchAll
|
||||||
|
}, {
|
||||||
|
exactSuffix: "def!",
|
||||||
|
order: CustomSortOrder.alphabeticalReverse,
|
||||||
|
type: CustomSortGroupType.ExactSuffix,
|
||||||
|
combineWithIdx: 2
|
||||||
|
}, {
|
||||||
|
exactText: "Hello :-)",
|
||||||
|
order: CustomSortOrder.alphabeticalReverse,
|
||||||
|
type: CustomSortGroupType.ExactName,
|
||||||
|
priority: 1,
|
||||||
|
combineWithIdx: 2
|
||||||
|
}, {
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}],
|
||||||
|
outsidersGroupIdx: 4,
|
||||||
|
priorityOrder: [3,0,1,2],
|
||||||
|
targetFoldersPaths: ['/']
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = determineSortingGroup(file, sortSpec)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result).toEqual({
|
||||||
|
groupIdx: 2, // Imposed by combined groups
|
||||||
|
isFolder: false,
|
||||||
|
sortString: "Hello :-).md",
|
||||||
|
ctimeNewest: MOCK_TIMESTAMP + 222,
|
||||||
|
ctimeOldest: MOCK_TIMESTAMP + 222,
|
||||||
|
mtime: MOCK_TIMESTAMP + 333,
|
||||||
|
path: 'Some parent folder/Hello :-).md'
|
||||||
|
});
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('determineFolderDatesIfNeeded', () => {
|
describe('determineFolderDatesIfNeeded', () => {
|
||||||
|
|
|
@ -230,6 +230,14 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
|
||||||
const idxAfterLastGroupIdx: number = spec.groups.length
|
const idxAfterLastGroupIdx: number = spec.groups.length
|
||||||
let determinedGroupIdx: number | undefined = determined ? groupIdx! : idxAfterLastGroupIdx
|
let determinedGroupIdx: number | undefined = determined ? groupIdx! : idxAfterLastGroupIdx
|
||||||
|
|
||||||
|
// Redirection to the first group of combined, if detected
|
||||||
|
if (determined) {
|
||||||
|
const combinedGroupIdx: number | undefined = spec.groups[determinedGroupIdx].combineWithIdx
|
||||||
|
if (combinedGroupIdx !== undefined) {
|
||||||
|
determinedGroupIdx = combinedGroupIdx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!determined) {
|
if (!determined) {
|
||||||
// Automatically assign the index to outsiders group, if relevant was configured
|
// Automatically assign the index to outsiders group, if relevant was configured
|
||||||
if (isDefined(spec.outsidersFilesGroupIdx) && aFile) {
|
if (isDefined(spec.outsidersFilesGroupIdx) && aFile) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
escapeRegexUnsafeCharacters,
|
escapeRegexUnsafeCharacters,
|
||||||
extractNumericSortingSymbol,
|
extractNumericSortingSymbol,
|
||||||
hasMoreThanOneNumericSortingSymbol,
|
hasMoreThanOneNumericSortingSymbol,
|
||||||
NumberNormalizerFn, ProblemCode,
|
NumberNormalizerFn,
|
||||||
RegexpUsedAs,
|
RegexpUsedAs,
|
||||||
RomanNumberNormalizerFn,
|
RomanNumberNormalizerFn,
|
||||||
SortingSpecProcessor
|
SortingSpecProcessor
|
||||||
|
@ -860,6 +860,42 @@ const expectedSortSpecForPriorityGroups2: { [key: string]: CustomSortSpec } = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const expectedSortSpecForPriorityAndCombineGroups: { [key: string]: CustomSortSpec } = {
|
||||||
|
"/": {
|
||||||
|
groups: [{
|
||||||
|
combineWithIdx: 0,
|
||||||
|
exactPrefix: "Fi",
|
||||||
|
filesOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
priority: 1,
|
||||||
|
type: CustomSortGroupType.ExactPrefix
|
||||||
|
}, {
|
||||||
|
combineWithIdx: 0,
|
||||||
|
exactPrefix: "Fo",
|
||||||
|
foldersOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
priority: 2,
|
||||||
|
type: CustomSortGroupType.ExactPrefix
|
||||||
|
}, {
|
||||||
|
exactSuffix: "def!",
|
||||||
|
priority: 3,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.ExactSuffix
|
||||||
|
}, {
|
||||||
|
exactText: "Anything",
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
priority: 1,
|
||||||
|
type: CustomSortGroupType.ExactName
|
||||||
|
}, {
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}],
|
||||||
|
outsidersGroupIdx: 4,
|
||||||
|
targetFoldersPaths: ['/'],
|
||||||
|
priorityOrder: [2,1,0,3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe('SortingSpecProcessor', () => {
|
describe('SortingSpecProcessor', () => {
|
||||||
let processor: SortingSpecProcessor;
|
let processor: SortingSpecProcessor;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -877,8 +913,127 @@ describe('SortingSpecProcessor', () => {
|
||||||
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForPriorityGroups2)
|
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForPriorityGroups2)
|
||||||
expect(result?.sortSpecByWildcard).toBeUndefined()
|
expect(result?.sortSpecByWildcard).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
it('should recognize the combine and priority prefixes in any order example 1', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
target-folder: /
|
||||||
|
/! /+ /:files Fi...
|
||||||
|
/!! /+ /folders Fo...
|
||||||
|
/!!! ...def!
|
||||||
|
/! Anything
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForPriorityAndCombineGroups)
|
||||||
|
expect(result?.sortSpecByWildcard).toBeUndefined()
|
||||||
|
})
|
||||||
|
it('should recognize the combine and priority prefixes in any order example 2', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
target-folder: /
|
||||||
|
/+ /! /:files Fi...
|
||||||
|
/+ /!! /folders Fo...
|
||||||
|
/!!! ...def!
|
||||||
|
/! Anything
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result?.sortSpecByPath).toEqual(expectedSortSpecForPriorityAndCombineGroups)
|
||||||
|
expect(result?.sortSpecByWildcard).toBeUndefined()
|
||||||
|
})
|
||||||
|
it('should accept the combine operator in single line only', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
target-folder: /
|
||||||
|
/+ /:files Fi...
|
||||||
|
/folders Fo...
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result?.sortSpecByPath).toEqual({
|
||||||
|
"/": {
|
||||||
|
groups: [{
|
||||||
|
combineWithIdx: 0,
|
||||||
|
exactPrefix: "Fi",
|
||||||
|
filesOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.ExactPrefix
|
||||||
|
}, {
|
||||||
|
exactPrefix: "Fo",
|
||||||
|
foldersOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.ExactPrefix
|
||||||
|
}, {
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}],
|
||||||
|
outsidersGroupIdx: 2,
|
||||||
|
targetFoldersPaths: ['/']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(result?.sortSpecByWildcard).toBeUndefined()
|
||||||
|
})
|
||||||
|
it('should correctly parse combine operator, apply default sorting and explicit sorting', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
target-folder: /
|
||||||
|
Nothing
|
||||||
|
> a-z
|
||||||
|
/+ /:files Fi...
|
||||||
|
/+ /folders Fo...
|
||||||
|
... Separator
|
||||||
|
/+ Abc...
|
||||||
|
/+ ...Def
|
||||||
|
/+ ...
|
||||||
|
> modified
|
||||||
|
Unreachable line
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result?.sortSpecByPath).toEqual({
|
||||||
|
"/": {
|
||||||
|
groups: [{
|
||||||
|
exactText: "Nothing",
|
||||||
|
order: CustomSortOrder.alphabeticalReverse,
|
||||||
|
type: CustomSortGroupType.ExactName
|
||||||
|
}, {
|
||||||
|
combineWithIdx: 1,
|
||||||
|
exactPrefix: "Fi",
|
||||||
|
filesOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.ExactPrefix
|
||||||
|
}, {
|
||||||
|
combineWithIdx: 1,
|
||||||
|
exactPrefix: "Fo",
|
||||||
|
foldersOnly: true,
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.ExactPrefix
|
||||||
|
}, {
|
||||||
|
exactSuffix: " Separator",
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.ExactSuffix
|
||||||
|
}, {
|
||||||
|
combineWithIdx: 4,
|
||||||
|
exactPrefix: "Abc",
|
||||||
|
order: CustomSortOrder.byModifiedTimeReverse,
|
||||||
|
type: CustomSortGroupType.ExactPrefix
|
||||||
|
}, {
|
||||||
|
combineWithIdx: 4,
|
||||||
|
exactSuffix: "Def",
|
||||||
|
order: CustomSortOrder.byModifiedTimeReverse,
|
||||||
|
type: CustomSortGroupType.ExactSuffix
|
||||||
|
}, {
|
||||||
|
combineWithIdx: 4,
|
||||||
|
order: CustomSortOrder.byModifiedTimeReverse,
|
||||||
|
type: CustomSortGroupType.MatchAll
|
||||||
|
}, {
|
||||||
|
exactText: "Unreachable line",
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.ExactName
|
||||||
|
}, {
|
||||||
|
order: CustomSortOrder.alphabetical,
|
||||||
|
type: CustomSortGroupType.Outsiders
|
||||||
|
}],
|
||||||
|
outsidersGroupIdx: 8,
|
||||||
|
targetFoldersPaths: ['/']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(result?.sortSpecByWildcard).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
const txtInputTargetFolderMultiSpecA: string = `
|
const txtInputTargetFolderMultiSpecA: string = `
|
||||||
target-folder: .
|
target-folder: .
|
||||||
|
@ -1231,10 +1386,6 @@ target-folder: AAA
|
||||||
sorting: standard
|
sorting: standard
|
||||||
`
|
`
|
||||||
|
|
||||||
const txtInputErrorPriorityAlone: string = `
|
|
||||||
/!
|
|
||||||
`
|
|
||||||
|
|
||||||
const txtInputErrorPriorityEmptyFilePattern: string = `
|
const txtInputErrorPriorityEmptyFilePattern: string = `
|
||||||
/!! /:
|
/!! /:
|
||||||
`
|
`
|
||||||
|
@ -1344,7 +1495,9 @@ describe('SortingSpecProcessor error detection and reporting', () => {
|
||||||
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT(' sorting: standard'))
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT(' sorting: standard'))
|
||||||
})
|
})
|
||||||
it('should recognize error: priority indicator alone', () => {
|
it('should recognize error: priority indicator alone', () => {
|
||||||
const inputTxtArr: Array<string> = txtInputErrorPriorityAlone.split('\n')
|
const inputTxtArr: Array<string> = `
|
||||||
|
/!
|
||||||
|
`.replace(/\t/gi, '').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).toBeNull()
|
expect(result).toBeNull()
|
||||||
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
@ -1352,6 +1505,28 @@ describe('SortingSpecProcessor error detection and reporting', () => {
|
||||||
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
|
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/!'))
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/!'))
|
||||||
})
|
})
|
||||||
|
it('should recognize error: multiple priority indicators alone', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/! /!! /!!!
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 16:TooManyPriorityPrefixes Only one priority prefix allowed on sorting group ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/! /!! /!!!'))
|
||||||
|
})
|
||||||
|
it('should recognize error: multiple priority indicators', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/!!! /!!! Abc\.d+ ...
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 16:TooManyPriorityPrefixes Only one priority prefix allowed on sorting group ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/!!! /!!! Abc\.d+ ...'))
|
||||||
|
})
|
||||||
it('should recognize error: priority indicator with empty file pattern', () => {
|
it('should recognize error: priority indicator with empty file pattern', () => {
|
||||||
const inputTxtArr: Array<string> = txtInputErrorPriorityEmptyFilePattern.split('\n')
|
const inputTxtArr: Array<string> = txtInputErrorPriorityEmptyFilePattern.split('\n')
|
||||||
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
@ -1379,6 +1554,98 @@ describe('SortingSpecProcessor error detection and reporting', () => {
|
||||||
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
|
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/! %'))
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/! %'))
|
||||||
})
|
})
|
||||||
|
it('should recognize error of combining: sorting order on first group', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/+ Abc
|
||||||
|
> modified
|
||||||
|
/+ /:files def
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(1)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 20:OnlyLastCombinedGroupCanSpecifyOrder Predecessor group of combined group cannot contain order specification. Put it at the last of group in combined groups ${ERR_SUFFIX}`)
|
||||||
|
})
|
||||||
|
it('should recognize error of combining: sorting order not on last group', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/+ Abc
|
||||||
|
/+ ...Def
|
||||||
|
/+ Ghi...
|
||||||
|
> modified
|
||||||
|
/+ /:files def
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(1)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 20:OnlyLastCombinedGroupCanSpecifyOrder Predecessor group of combined group cannot contain order specification. Put it at the last of group in combined groups ${ERR_SUFFIX}`)
|
||||||
|
})
|
||||||
|
it('should recognize error of combining: combining not allowed for outsiders group', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/+ %
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 17:CombiningNotAllowedOnOutsidersGroup Combining is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/+ %'))
|
||||||
|
})
|
||||||
|
it('should recognize error of combining: combining not allowed for outsiders priority group', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/+ /! /
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 15:PriorityNotAllowedOnOutsidersGroup Priority is not allowed for sorting group with empty match-pattern ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/+ /! /'))
|
||||||
|
})
|
||||||
|
it('should recognize error of combining: multiple combine operators', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/+ /! /+ /: Something
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 18:TooManyCombinePrefixes Only one combining prefix allowed on sorting group ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/+ /! /+ /: Something'))
|
||||||
|
})
|
||||||
|
it('should recognize error: too many sorting group type prefixes', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/folders /:files Hello
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 21:TooManyGroupTypePrefixes Only one sorting group type prefix allowed on sorting group ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/folders /:files Hello'))
|
||||||
|
})
|
||||||
|
it('should recognize error: priority prefix after sorting group type prefixe', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/folders /+ /! Hello
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 22:PriorityPrefixAfterGroupTypePrefix Priority prefix must be used before sorting group type indicator ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/folders /+ /! Hello'))
|
||||||
|
})
|
||||||
|
it('should recognize error: combine prefix after sorting group type prefixe', () => {
|
||||||
|
const inputTxtArr: Array<string> = `
|
||||||
|
/folders /+ Hello
|
||||||
|
`.replace(/\t/gi, '').split('\n')
|
||||||
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
expect(errorsLogger).toHaveBeenCalledTimes(2)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(1,
|
||||||
|
`${ERR_PREFIX} 23:CombinePrefixAfterGroupTypePrefix Combining prefix must be used before sorting group type indicator ${ERR_SUFFIX_IN_LINE(2)}`)
|
||||||
|
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT('/folders /+ Hello'))
|
||||||
|
})
|
||||||
it('should recognize empty spec', () => {
|
it('should recognize empty spec', () => {
|
||||||
const inputTxtArr: Array<string> = txtInputEmptySpec.split('\n')
|
const inputTxtArr: Array<string> = txtInputEmptySpec.split('\n')
|
||||||
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
|
||||||
|
|
|
@ -47,6 +47,7 @@ interface ParsedSortingGroup {
|
||||||
outsidersGroup?: boolean // Mutually exclusive with plainSpec and arraySpec
|
outsidersGroup?: boolean // Mutually exclusive with plainSpec and arraySpec
|
||||||
itemToHide?: boolean
|
itemToHide?: boolean
|
||||||
priority?: number
|
priority?: number
|
||||||
|
combine?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProblemCode {
|
export enum ProblemCode {
|
||||||
|
@ -65,12 +66,21 @@ export enum ProblemCode {
|
||||||
ItemToHideNoSupportForThreeDots,
|
ItemToHideNoSupportForThreeDots,
|
||||||
DuplicateWildcardSortSpecForSameFolder,
|
DuplicateWildcardSortSpecForSameFolder,
|
||||||
StandardObsidianSortAllowedOnlyAtFolderLevel,
|
StandardObsidianSortAllowedOnlyAtFolderLevel,
|
||||||
PriorityNotAllowedOnOutsidersGroup
|
PriorityNotAllowedOnOutsidersGroup,
|
||||||
|
TooManyPriorityPrefixes,
|
||||||
|
CombiningNotAllowedOnOutsidersGroup,
|
||||||
|
TooManyCombinePrefixes,
|
||||||
|
ModifierPrefixesOnlyOnOutsidersGroup,
|
||||||
|
OnlyLastCombinedGroupCanSpecifyOrder,
|
||||||
|
TooManyGroupTypePrefixes,
|
||||||
|
PriorityPrefixAfterGroupTypePrefix,
|
||||||
|
CombinePrefixAfterGroupTypePrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContextFreeProblems = new Set<ProblemCode>([
|
const ContextFreeProblems = new Set<ProblemCode>([
|
||||||
ProblemCode.DuplicateSortSpecForSameFolder,
|
ProblemCode.DuplicateSortSpecForSameFolder,
|
||||||
ProblemCode.DuplicateWildcardSortSpecForSameFolder
|
ProblemCode.DuplicateWildcardSortSpecForSameFolder,
|
||||||
|
ProblemCode.OnlyLastCombinedGroupCanSpecifyOrder
|
||||||
])
|
])
|
||||||
|
|
||||||
const ThreeDots = '...';
|
const ThreeDots = '...';
|
||||||
|
@ -199,6 +209,12 @@ const SortingGroupPriorityPrefixes: { [key: string]: number } = {
|
||||||
[PriorityModifierPrio3Lexeme]: PRIO_3
|
[PriorityModifierPrio3Lexeme]: PRIO_3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CombineGroupLexeme: string = '/+'
|
||||||
|
|
||||||
|
const CombiningGroupPrefixes: Array<string> = [
|
||||||
|
CombineGroupLexeme
|
||||||
|
]
|
||||||
|
|
||||||
interface SortingGroupType {
|
interface SortingGroupType {
|
||||||
filesOnly?: boolean
|
filesOnly?: boolean
|
||||||
filenameWithExt?: boolean // The text matching criteria should apply to filename + extension
|
filenameWithExt?: boolean // The text matching criteria should apply to filename + extension
|
||||||
|
@ -490,7 +506,9 @@ export class SortingSpecProcessor {
|
||||||
if (success) {
|
if (success) {
|
||||||
if (this.ctx.specs.length > 0) {
|
if (this.ctx.specs.length > 0) {
|
||||||
for (let spec of this.ctx.specs) {
|
for (let spec of this.ctx.specs) {
|
||||||
this.postprocessSortSpec(spec)
|
if (!this.postprocessSortSpec(spec)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sortspecByWildcard: FolderWildcardMatching<CustomSortSpec> | undefined
|
let sortspecByWildcard: FolderWildcardMatching<CustomSortSpec> | undefined
|
||||||
|
@ -690,78 +708,149 @@ export class SortingSpecProcessor {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const priorityPrefixAlone: number = SortingGroupPriorityPrefixes[s]
|
|
||||||
if (priorityPrefixAlone) {
|
|
||||||
this.problem(ProblemCode.PriorityNotAllowedOnOutsidersGroup, 'Priority is not allowed for sorting group with empty match-pattern')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
let groupPriority: number | undefined = undefined
|
let groupPriority: number | undefined = undefined
|
||||||
|
let groupPriorityPrefixesCount: number = 0
|
||||||
|
let combineGroup: boolean | undefined = undefined
|
||||||
|
let combineGroupPrefixesCount: number = 0
|
||||||
|
let groupType: SortingGroupType | undefined = undefined
|
||||||
|
let groupTypePrefixesCount: number = 0
|
||||||
|
let priorityPrefixAfterGroupTypePrefix: boolean = false
|
||||||
|
let combinePrefixAfterGroupTypePrefix: boolean = false
|
||||||
|
|
||||||
|
let prefixRecognized: boolean | undefined = undefined
|
||||||
|
while (prefixRecognized === undefined || prefixRecognized) {
|
||||||
|
let doContinue: boolean = false // to support 'continue' on external loop from nested loop
|
||||||
|
|
||||||
for (const priorityPrefix of Object.keys(SortingGroupPriorityPrefixes)) {
|
for (const priorityPrefix of Object.keys(SortingGroupPriorityPrefixes)) {
|
||||||
if (s.startsWith(priorityPrefix + ' ')) {
|
if (s === priorityPrefix || s.startsWith(priorityPrefix + ' ')) {
|
||||||
groupPriority = SortingGroupPriorityPrefixes[priorityPrefix]
|
groupPriority = SortingGroupPriorityPrefixes[priorityPrefix]
|
||||||
|
groupPriorityPrefixesCount ++
|
||||||
|
prefixRecognized = true
|
||||||
|
doContinue = true
|
||||||
|
if (groupType) {
|
||||||
|
priorityPrefixAfterGroupTypePrefix = true
|
||||||
|
}
|
||||||
s = s.substring(priorityPrefix.length).trim()
|
s = s.substring(priorityPrefix.length).trim()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefixAlone: SortingGroupType = SortingGroupPrefixes[s]
|
if (doContinue) continue
|
||||||
if (prefixAlone) {
|
|
||||||
if (prefixAlone.itemToHide) {
|
for (let combinePrefix of CombiningGroupPrefixes) {
|
||||||
this.problem(ProblemCode.ItemToHideExactNameWithExtRequired, 'Exact name with ext of file or folders to hide is required')
|
if (s === combinePrefix || s.startsWith(combinePrefix + ' ')) {
|
||||||
|
combineGroup = true
|
||||||
|
combineGroupPrefixesCount ++
|
||||||
|
prefixRecognized = true
|
||||||
|
doContinue = true
|
||||||
|
if (groupType) {
|
||||||
|
combinePrefixAfterGroupTypePrefix = true
|
||||||
|
}
|
||||||
|
s = s.substring(combinePrefix.length).trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doContinue) continue
|
||||||
|
|
||||||
|
for (const sortingGroupTypePrefix of Object.keys(SortingGroupPrefixes)) {
|
||||||
|
if (s === sortingGroupTypePrefix || s.startsWith(sortingGroupTypePrefix + ' ')) {
|
||||||
|
groupType = SortingGroupPrefixes[sortingGroupTypePrefix]
|
||||||
|
groupTypePrefixesCount++
|
||||||
|
prefixRecognized = true
|
||||||
|
doContinue = true
|
||||||
|
s = s.substring(sortingGroupTypePrefix.length).trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doContinue) continue
|
||||||
|
|
||||||
|
prefixRecognized = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupPriorityPrefixesCount > 1) {
|
||||||
|
this.problem(ProblemCode.TooManyPriorityPrefixes, 'Only one priority prefix allowed on sorting group')
|
||||||
return null
|
return null
|
||||||
} else { // !prefixAlone.itemToHide
|
}
|
||||||
if (groupPriority) {
|
|
||||||
|
if (s === '' && groupPriority) {
|
||||||
this.problem(ProblemCode.PriorityNotAllowedOnOutsidersGroup, 'Priority is not allowed for sorting group with empty match-pattern')
|
this.problem(ProblemCode.PriorityNotAllowedOnOutsidersGroup, 'Priority is not allowed for sorting group with empty match-pattern')
|
||||||
return null
|
return null
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (combineGroupPrefixesCount > 1) {
|
||||||
|
this.problem(ProblemCode.TooManyCombinePrefixes, 'Only one combining prefix allowed on sorting group')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s === '' && combineGroup) {
|
||||||
|
this.problem(ProblemCode.CombiningNotAllowedOnOutsidersGroup, 'Combining is not allowed for sorting group with empty match-pattern')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupTypePrefixesCount > 1) {
|
||||||
|
this.problem(ProblemCode.TooManyGroupTypePrefixes, 'Only one sorting group type prefix allowed on sorting group')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priorityPrefixAfterGroupTypePrefix) {
|
||||||
|
this.problem(ProblemCode.PriorityPrefixAfterGroupTypePrefix, 'Priority prefix must be used before sorting group type indicator')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (combinePrefixAfterGroupTypePrefix) {
|
||||||
|
this.problem(ProblemCode.CombinePrefixAfterGroupTypePrefix, 'Combining prefix must be used before sorting group type indicator')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s === '' && groupType) { // alone alone alone
|
||||||
|
if (groupType.itemToHide) {
|
||||||
|
this.problem(ProblemCode.ItemToHideExactNameWithExtRequired, 'Exact name with ext of file or folders to hide is required')
|
||||||
|
return null
|
||||||
|
} else { // !sortingGroupIndicatorPrefixAlone.itemToHide
|
||||||
return {
|
return {
|
||||||
outsidersGroup: true,
|
outsidersGroup: true,
|
||||||
filesOnly: prefixAlone.filesOnly,
|
filesOnly: groupType.filesOnly,
|
||||||
foldersOnly: prefixAlone.foldersOnly
|
foldersOnly: groupType.foldersOnly
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const prefix of Object.keys(SortingGroupPrefixes)) {
|
if (groupType) {
|
||||||
if (s.startsWith(prefix + ' ')) {
|
if (groupType.itemToHide) {
|
||||||
const sortingGroupType: SortingGroupType = SortingGroupPrefixes[prefix]
|
|
||||||
if (sortingGroupType.itemToHide) {
|
|
||||||
return {
|
return {
|
||||||
itemToHide: true,
|
itemToHide: true,
|
||||||
plainSpec: s.substring(prefix.length + 1),
|
plainSpec: s,
|
||||||
filesOnly: sortingGroupType.filesOnly,
|
filesOnly: groupType.filesOnly,
|
||||||
foldersOnly: sortingGroupType.foldersOnly
|
foldersOnly: groupType.foldersOnly
|
||||||
}
|
}
|
||||||
} else { // !sortingGroupType.itemToHide
|
} else { // !sortingGroupType.itemToHide
|
||||||
return {
|
return {
|
||||||
plainSpec: s.substring(prefix.length + 1),
|
plainSpec: s,
|
||||||
filesOnly: sortingGroupType.filesOnly,
|
filesOnly: groupType.filesOnly,
|
||||||
foldersOnly: sortingGroupType.foldersOnly,
|
foldersOnly: groupType.foldersOnly,
|
||||||
matchFilenameWithExt: sortingGroupType.filenameWithExt,
|
matchFilenameWithExt: groupType.filenameWithExt,
|
||||||
priority: groupPriority ?? undefined
|
priority: groupPriority ?? undefined,
|
||||||
}
|
combine: combineGroup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupPriority) {
|
if ((groupPriority || combineGroup) && s !== '' ) {
|
||||||
if (s === '') {
|
// Edge case: line with only priority prefix or combine prefix and no other known syntax, yet some content
|
||||||
// Edge case: line with only priority prefix and no other content
|
|
||||||
this.problem(ProblemCode.PriorityNotAllowedOnOutsidersGroup, 'Priority is not allowed for sorting group with empty match-pattern')
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
// Edge case: line with only priority prefix and no other known syntax, yet some content
|
|
||||||
return {
|
return {
|
||||||
plainSpec: s,
|
plainSpec: s,
|
||||||
priority: groupPriority
|
priority: groupPriority,
|
||||||
}
|
combine: combineGroup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Artificial value used to indicate not-undefined value in if (COMBINING_INDICATOR_IDX) { ... }
|
||||||
|
COMBINING_INDICATOR_IDX: number = -1
|
||||||
|
|
||||||
private processParsedSortGroupSpec(group: ParsedSortingGroup): boolean {
|
private processParsedSortGroupSpec(group: ParsedSortingGroup): boolean {
|
||||||
if (!this.ctx.currentSpec) {
|
if (!this.ctx.currentSpec) {
|
||||||
this.ctx.currentSpec = this.putNewSpecForNewTargetFolder()
|
this.ctx.currentSpec = this.putNewSpecForNewTargetFolder()
|
||||||
|
@ -791,7 +880,10 @@ export class SortingSpecProcessor {
|
||||||
newGroup.priority = group.priority
|
newGroup.priority = group.priority
|
||||||
this.addExpediteGroupInfo(this.ctx.currentSpec, group.priority, groupIdx)
|
this.addExpediteGroupInfo(this.ctx.currentSpec, group.priority, groupIdx)
|
||||||
}
|
}
|
||||||
|
// Consume combined group
|
||||||
|
if (group.combine) {
|
||||||
|
newGroup.combineWithIdx = this.COMBINING_INDICATOR_IDX
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
@ -805,7 +897,7 @@ export class SortingSpecProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private postprocessSortSpec(spec: CustomSortSpec): void {
|
private postprocessSortSpec(spec: CustomSortSpec): boolean {
|
||||||
// clean up to prevent false warnings in console
|
// clean up to prevent false warnings in console
|
||||||
spec.outsidersGroupIdx = undefined
|
spec.outsidersGroupIdx = undefined
|
||||||
spec.outsidersFilesGroupIdx = undefined
|
spec.outsidersFilesGroupIdx = undefined
|
||||||
|
@ -853,6 +945,55 @@ export class SortingSpecProcessor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process 'combined groups'
|
||||||
|
let anyCombinedGroupPresent: boolean = false
|
||||||
|
let currentCombinedGroupIdx: number | undefined = undefined
|
||||||
|
for (let i=0; i<spec.groups.length; i++) {
|
||||||
|
const group: CustomSortGroup = spec.groups[i]
|
||||||
|
if (group.combineWithIdx === this.COMBINING_INDICATOR_IDX) { // Here we expect the COMBINING_INDICATOR_IDX artificial value or undefined
|
||||||
|
if (currentCombinedGroupIdx === undefined) {
|
||||||
|
currentCombinedGroupIdx = i
|
||||||
|
} else {
|
||||||
|
// Ensure that the preceding group doesn't contain sorting order
|
||||||
|
if (spec.groups[i - 1].order) {
|
||||||
|
this.problem(ProblemCode.OnlyLastCombinedGroupCanSpecifyOrder, 'Predecessor group of combined group cannot contain order specification. Put it at the last of group in combined groups')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.combineWithIdx = currentCombinedGroupIdx
|
||||||
|
anyCombinedGroupPresent = true
|
||||||
|
} else {
|
||||||
|
currentCombinedGroupIdx = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate sorting order within combined groups
|
||||||
|
if (anyCombinedGroupPresent) {
|
||||||
|
let orderForCombinedGroup: CustomSortOrder | undefined
|
||||||
|
let byMetadataFieldForCombinedGroup: string | undefined
|
||||||
|
let idxOfCurrentCombinedGroup: number | undefined = undefined
|
||||||
|
for (let i = spec.groups.length - 1; i >= 0; i--) {
|
||||||
|
const group: CustomSortGroup = spec.groups[i]
|
||||||
|
|
||||||
|
if (group.combineWithIdx !== undefined) {
|
||||||
|
if (group.combineWithIdx === idxOfCurrentCombinedGroup) { // a subsequent (2nd, 3rd, ...) group of combined (counting from the end)
|
||||||
|
group.order = orderForCombinedGroup
|
||||||
|
group.byMetadataField = byMetadataFieldForCombinedGroup
|
||||||
|
} else { // the first group of combined (counting from the end)
|
||||||
|
idxOfCurrentCombinedGroup = group.combineWithIdx
|
||||||
|
orderForCombinedGroup = group.order // could be undefined
|
||||||
|
byMetadataFieldForCombinedGroup = group.byMetadataField // could be undefined
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// for sanity
|
||||||
|
idxOfCurrentCombinedGroup = undefined
|
||||||
|
orderForCombinedGroup = undefined
|
||||||
|
byMetadataFieldForCombinedGroup = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Populate sorting order down the hierarchy for more clean sorting logic later on
|
// Populate sorting order down the hierarchy for more clean sorting logic later on
|
||||||
for (let group of spec.groups) {
|
for (let group of spec.groups) {
|
||||||
if (!group.order) {
|
if (!group.order) {
|
||||||
|
@ -883,6 +1024,8 @@ export class SortingSpecProcessor {
|
||||||
spec.targetFoldersPaths[idx] = `${this.ctx.folderPath}/${path.substring(CURRENT_FOLDER_PREFIX.length)}`
|
spec.targetFoldersPaths[idx] = `${this.ctx.folderPath}/${path.substring(CURRENT_FOLDER_PREFIX.length)}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return true // success indicator
|
||||||
}
|
}
|
||||||
|
|
||||||
// level 2 parser functions defined in order of occurrence and dependency
|
// level 2 parser functions defined in order of occurrence and dependency
|
||||||
|
|
Loading…
Reference in New Issue