From 581f5e9f36ae43bfab4821c2f3899262ca686724 Mon Sep 17 00:00:00 2001
From: SebastianMC <23032356+SebastianMC@users.noreply.github.com>
Date: Tue, 29 Nov 2022 10:17:15 +0100
Subject: [PATCH] #29 - Feature: priorities of sorting rules (#31)
- Implementation with full coverage of unit tests
- Documentation update with details about priorities (as an advanced feature, only in manual.md, not in README.md)
---
docs/manual.md | 66 +++++++
docs/svg/priorities-example-a.svg | 60 ++++++
docs/svg/priorities-example-b.svg | 60 ++++++
src/custom-sort/custom-sort-types.ts | 4 +-
src/custom-sort/custom-sort.spec.ts | 46 +++++
src/custom-sort/custom-sort.ts | 13 +-
.../sorting-spec-processor.spec.ts | 184 +++++++++++++++++-
src/custom-sort/sorting-spec-processor.ts | 108 +++++++++-
8 files changed, 520 insertions(+), 21 deletions(-)
create mode 100644 docs/svg/priorities-example-a.svg
create mode 100644 docs/svg/priorities-example-b.svg
diff --git a/docs/manual.md b/docs/manual.md
index b9968e6..7f92291 100644
--- a/docs/manual.md
+++ b/docs/manual.md
@@ -1,3 +1,69 @@
Yet to be filled with content ;-)
See [syntax-reference.md](./syntax-reference.md), maybe that file has already some content?
+
+---
+Some sections added ad-hoc, to be integrated later
+
+# Advanced features
+
+## Priorities of sorting groups
+
+At run-time, when the custom sorting is triggered (explicitly or automatically) each folder item (a file or a sub-folder) is evaluated against the sorting groups.
+The evaluation (matching) is done in the order in which the sorting groups are defined in `sorting-spec: |` for the folder.
+
+That means, for example, that the sorting group `/:files ...` will match _all_ files - in turn, none of files has a chance to match further rule
+
+Consider the below example:
+```yaml
+---
+sorting-spec: |
+ target-folder: Some folder
+ // The below sorting group captures (matches) all files
+ /:files ...
+ // The below sorting group should (theoretically) capture files with names starting with 'Archive' word
+ // yet none of files will have a chance to reach the rule, because the previous sorting group will match all files
+ // Hence, the below sorting group is void
+ /:files Archive...
+---
+```
+
+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 `/!!!`
+
+The modified example would be:
+```yaml
+---
+sorting-spec: |
+ target-folder: Some folder
+ // The below sorting group captures (matches) all files
+ /:files ...
+ // The below sorting group captures files with names starting with 'Archive' word
+ // and thanks to the priority indicator prefix '/!' folder items are matched against it
+ // before matching the previous sorting group
+ /! /:files Archive...
+---
+```
+
+and it would result in the expected order of items:
+
+
+
+For clarity: the three available prefixes `/!` and `/!!` and `/!!!` allow for futher finetuning of sorting groups matching order, the `/!!!` representing the highest priority value
+
+> A SIDE NOTE
+>
+> In the above simplistic example, correct grouping of items can also be achieved in a different way:
+> instead of using priorities, the first sorting group could be expressed differently as `/:files` (no following `...` wildcard):
+> ```yaml
+> ---
+> sorting-spec: |
+> target-folder: Some folder
+> /:files
+> /:files Archive...
+> ---
+> ```
+> The sorting group expressed as `/:files` alone acts as a sorting group 'catch-all-files, which don't match any other sorting rule for the folder'
diff --git a/docs/svg/priorities-example-a.svg b/docs/svg/priorities-example-a.svg
new file mode 100644
index 0000000..4b911c2
--- /dev/null
+++ b/docs/svg/priorities-example-a.svg
@@ -0,0 +1,60 @@
+
+
+
diff --git a/docs/svg/priorities-example-b.svg b/docs/svg/priorities-example-b.svg
new file mode 100644
index 0000000..35246a8
--- /dev/null
+++ b/docs/svg/priorities-example-b.svg
@@ -0,0 +1,60 @@
+
+
+
diff --git a/src/custom-sort/custom-sort-types.ts b/src/custom-sort/custom-sort-types.ts
index 1ce15b3..5515373 100644
--- a/src/custom-sort/custom-sort-types.ts
+++ b/src/custom-sort/custom-sort-types.ts
@@ -57,6 +57,7 @@ export interface CustomSortGroup {
matchFilenameWithExt?: boolean
foldersOnly?: boolean
withMetadataFieldName?: string // for 'with-metadata:'
+ priority?: number
}
export interface CustomSortSpec {
@@ -68,9 +69,10 @@ export interface CustomSortSpec {
outsidersFilesGroupIdx?: number
outsidersFoldersGroupIdx?: number
itemsToHide?: Set
- plugin?: Plugin // to hand over the access to App instance to the sorting engine
+ priorityOrder?: Array // Indexes of groups in evaluation order
// For internal transient use
+ plugin?: Plugin // to hand over the access to App instance to the sorting engine
_mCache?: MetadataCache
}
diff --git a/src/custom-sort/custom-sort.spec.ts b/src/custom-sort/custom-sort.spec.ts
index 3809eb3..5ae7501 100644
--- a/src/custom-sort/custom-sort.spec.ts
+++ b/src/custom-sort/custom-sort.spec.ts
@@ -788,6 +788,52 @@ describe('determineSortingGroup', () => {
} as FolderItemForSorting);
})
})
+
+ it('should correctly apply priority group', () => {
+ // given
+ const file: TFile = mockTFile('Abcdef!', '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!",
+ priority: 2,
+ order: CustomSortOrder.alphabetical,
+ type: CustomSortGroupType.ExactSuffix
+ }, {
+ exactText: "Abcdef!",
+ order: CustomSortOrder.alphabetical,
+ priority: 3,
+ type: CustomSortGroupType.ExactName
+ }, {
+ order: CustomSortOrder.alphabetical,
+ type: CustomSortGroupType.Outsiders
+ }],
+ outsidersGroupIdx: 4,
+ targetFoldersPaths: ['/'],
+ priorityOrder: [3,2,0,1]
+ }
+
+ // when
+ const result = determineSortingGroup(file, sortSpec)
+
+ // then
+ expect(result).toEqual({
+ groupIdx: 3,
+ isFolder: false,
+ sortString: "Abcdef!.md",
+ ctimeNewest: MOCK_TIMESTAMP + 222,
+ ctimeOldest: MOCK_TIMESTAMP + 222,
+ mtime: MOCK_TIMESTAMP + 333,
+ path: 'Some parent folder/Abcdef!.md'
+ });
+ })
})
describe('determineFolderDatesIfNeeded', () => {
diff --git a/src/custom-sort/custom-sort.ts b/src/custom-sort/custom-sort.ts
index 4f3f6e8..1fe007c 100644
--- a/src/custom-sort/custom-sort.ts
+++ b/src/custom-sort/custom-sort.ts
@@ -129,8 +129,13 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
const entryAsTFile: TFile = entry as TFile
const basename: string = aFolder ? entry.name : entryAsTFile.basename
- for (groupIdx = 0; groupIdx < spec.groups.length; groupIdx++) {
+ // When priorities come in play, the ordered list of groups to check could be shorter
+ // than the actual full set of defined groups, because the outsiders group are not
+ // in the ordered list (aka priorityOrder array)
+ const numOfGroupsToCheck: number = spec.priorityOrder ? spec.priorityOrder.length : spec.groups.length
+ for (let idx = 0; idx < numOfGroupsToCheck; idx++) {
matchedGroup = null
+ groupIdx = spec.priorityOrder ? spec.priorityOrder[idx] : idx
const group: CustomSortGroup = spec.groups[groupIdx];
if (group.foldersOnly && aFile) continue;
if (group.filesOnly && aFolder) continue;
@@ -218,12 +223,12 @@ export const determineSortingGroup = function (entry: TFile | TFolder, spec: Cus
break
}
if (determined) {
- break;
+ break; // No need to check other sorting groups
}
}
- // the final groupIdx for undetermined folder entry is either the last+1 groupIdx or idx of explicitly defined outsiders group
- let determinedGroupIdx: number | undefined = groupIdx;
+ const idxAfterLastGroupIdx: number = spec.groups.length
+ let determinedGroupIdx: number | undefined = determined ? groupIdx! : idxAfterLastGroupIdx
if (!determined) {
// Automatically assign the index to outsiders group, if relevant was configured
diff --git a/src/custom-sort/sorting-spec-processor.spec.ts b/src/custom-sort/sorting-spec-processor.spec.ts
index 34c899d..7085c96 100644
--- a/src/custom-sort/sorting-spec-processor.spec.ts
+++ b/src/custom-sort/sorting-spec-processor.spec.ts
@@ -7,7 +7,7 @@ import {
escapeRegexUnsafeCharacters,
extractNumericSortingSymbol,
hasMoreThanOneNumericSortingSymbol,
- NumberNormalizerFn,
+ NumberNormalizerFn, ProblemCode,
RegexpUsedAs,
RomanNumberNormalizerFn,
SortingSpecProcessor
@@ -535,7 +535,7 @@ describe('SortingSpecProcessor', () => {
const txtInputSimplistic1: string = `
target-folder: /*
/:files
-//folders
+/folders
`
const expectedSortSpecForSimplistic1: { [key: string]: CustomSortSpec } = {
@@ -545,11 +545,12 @@ const expectedSortSpecForSimplistic1: { [key: string]: CustomSortSpec } = {
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}, {
+ foldersOnly: true,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}],
outsidersFilesGroupIdx: 0,
- outsidersGroupIdx: 1,
+ outsidersFoldersGroupIdx: 1,
targetFoldersPaths: ['/*']
}
}
@@ -561,11 +562,12 @@ const expectedWildcardMatchingTreeForSimplistic1 = {
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}, {
+ foldersOnly: true,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}],
outsidersFilesGroupIdx: 0,
- outsidersGroupIdx: 1,
+ outsidersFoldersGroupIdx: 1,
targetFoldersPaths: ['/*']
},
"subtree": {}
@@ -574,7 +576,7 @@ const expectedWildcardMatchingTreeForSimplistic1 = {
const txtInputSimplistic2: string = `
target-folder: /
/:files
-//folders
+/folders
`
const expectedSortSpecForSimplistic2: { [key: string]: CustomSortSpec } = {
@@ -584,11 +586,12 @@ const expectedSortSpecForSimplistic2: { [key: string]: CustomSortSpec } = {
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}, {
+ foldersOnly: true,
order: CustomSortOrder.alphabetical,
type: CustomSortGroupType.Outsiders
}],
outsidersFilesGroupIdx: 0,
- outsidersGroupIdx: 1,
+ outsidersFoldersGroupIdx: 1,
targetFoldersPaths: ['/']
}
}
@@ -760,6 +763,123 @@ describe('SortingSpecProcessor edge case', () => {
})
})
+const txtInputPriorityGroups1: string = `
+target-folder: /
+/:files
+/folders
+/! /:files Fi...
+/!! /folders Fo...
+/!!! ...def!
+Plain text
+/! % Anything
+`
+
+const txtInputPriorityGroups2: string = `
+target-folder: /
+/! /:files Fi...
+/!! /folders Fo...
+/!!! ...def!
+/! Anything
+`
+
+const expectedSortSpecForPriorityGroups1: { [key: string]: CustomSortSpec } = {
+ "/": {
+ groups: [{
+ filesOnly: true,
+ order: CustomSortOrder.alphabetical,
+ type: CustomSortGroupType.Outsiders
+ }, {
+ foldersOnly: true,
+ order: CustomSortOrder.alphabetical,
+ type: CustomSortGroupType.Outsiders
+ }, {
+ exactPrefix: "Fi",
+ filesOnly: true,
+ order: CustomSortOrder.alphabetical,
+ priority: 1,
+ type: CustomSortGroupType.ExactPrefix
+ }, {
+ exactPrefix: "Fo",
+ foldersOnly: true,
+ order: CustomSortOrder.alphabetical,
+ priority: 2,
+ type: CustomSortGroupType.ExactPrefix
+ }, {
+ exactSuffix: "def!",
+ priority: 3,
+ order: CustomSortOrder.alphabetical,
+ type: CustomSortGroupType.ExactSuffix
+ }, {
+ exactText: "Plain text",
+ order: CustomSortOrder.alphabetical,
+ type: CustomSortGroupType.ExactName
+ },{
+ exactText: "Anything",
+ order: CustomSortOrder.alphabetical,
+ priority: 1,
+ type: CustomSortGroupType.ExactName
+ }],
+ outsidersFilesGroupIdx: 0,
+ outsidersFoldersGroupIdx: 1,
+ targetFoldersPaths: ['/'],
+ priorityOrder: [4,3,2,6,5]
+ }
+}
+
+const expectedSortSpecForPriorityGroups2: { [key: string]: CustomSortSpec } = {
+ "/": {
+ groups: [{
+ exactPrefix: "Fi",
+ filesOnly: true,
+ order: CustomSortOrder.alphabetical,
+ priority: 1,
+ type: CustomSortGroupType.ExactPrefix
+ }, {
+ 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', () => {
+ let processor: SortingSpecProcessor;
+ beforeEach(() => {
+ processor = new SortingSpecProcessor();
+ });
+ it('should recognize the sorting groups with priority example 1', () => {
+ const inputTxtArr: Array = txtInputPriorityGroups1.split('\n')
+ const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
+ expect(result?.sortSpecByPath).toEqual(expectedSortSpecForPriorityGroups1)
+ expect(result?.sortSpecByWildcard).toBeUndefined()
+ })
+ it('should recognize the sorting groups with priority example 2', () => {
+ const inputTxtArr: Array = txtInputPriorityGroups2.split('\n')
+ const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
+ expect(result?.sortSpecByPath).toEqual(expectedSortSpecForPriorityGroups2)
+ expect(result?.sortSpecByWildcard).toBeUndefined()
+ })
+})
+
+
const txtInputTargetFolderMultiSpecA: string = `
target-folder: .
< a-z
@@ -1111,6 +1231,22 @@ target-folder: AAA
sorting: standard
`
+const txtInputErrorPriorityAlone: string = `
+/!
+`
+
+const txtInputErrorPriorityEmptyFilePattern: string = `
+/!! /:
+`
+
+const txtInputErrorPriorityEmptyFolderPattern: string = `
+/!!! /
+`
+
+const txtInputErrorPriorityEmptyPattern: string = `
+/! %
+`
+
const txtInputEmptySpec: string = ``
describe('SortingSpecProcessor error detection and reporting', () => {
@@ -1207,6 +1343,42 @@ describe('SortingSpecProcessor error detection and reporting', () => {
`${ERR_PREFIX} 14:StandardObsidianSortAllowedOnlyAtFolderLevel The standard Obsidian sort order is only allowed at a folder level (not nested syntax) ${ERR_SUFFIX_IN_LINE(4)}`)
expect(errorsLogger).toHaveBeenNthCalledWith(2, ERR_LINE_TXT(' sorting: standard'))
})
+ it('should recognize error: priority indicator alone', () => {
+ const inputTxtArr: Array = txtInputErrorPriorityAlone.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: priority indicator with empty file pattern', () => {
+ const inputTxtArr: Array = txtInputErrorPriorityEmptyFilePattern.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: priority indicator with empty folder pattern', () => {
+ const inputTxtArr: Array = txtInputErrorPriorityEmptyFolderPattern.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: priority indicator with empty pattern', () => {
+ const inputTxtArr: Array = txtInputErrorPriorityEmptyPattern.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 empty spec', () => {
const inputTxtArr: Array = txtInputEmptySpec.split('\n')
const result = processor.parseSortSpecFromText(inputTxtArr, 'mock-folder', 'custom-name-note.md')
diff --git a/src/custom-sort/sorting-spec-processor.ts b/src/custom-sort/sorting-spec-processor.ts
index f4aa3f1..da1f85a 100644
--- a/src/custom-sort/sorting-spec-processor.ts
+++ b/src/custom-sort/sorting-spec-processor.ts
@@ -46,6 +46,7 @@ interface ParsedSortingGroup {
arraySpec?: Array
outsidersGroup?: boolean // Mutually exclusive with plainSpec and arraySpec
itemToHide?: boolean
+ priority?: number
}
export enum ProblemCode {
@@ -63,7 +64,8 @@ export enum ProblemCode {
ItemToHideExactNameWithExtRequired,
ItemToHideNoSupportForThreeDots,
DuplicateWildcardSortSpecForSameFolder,
- StandardObsidianSortAllowedOnlyAtFolderLevel
+ StandardObsidianSortAllowedOnlyAtFolderLevel,
+ PriorityNotAllowedOnOutsidersGroup
}
const ContextFreeProblems = new Set([
@@ -174,7 +176,8 @@ const FilesWithExtGroupVerboseLexeme: string = '/:files.'
const FilesWithExtGroupShortLexeme: string = '/:.'
const FoldersGroupVerboseLexeme: string = '/folders'
const FoldersGroupShortLexeme: string = '/'
-const AnyTypeGroupLexeme: string = '%' // See % as a combination of / and :
+const AnyTypeGroupLexemeShort: string = '%' // See % as a combination of / and :
+const AnyTypeGroupLexeme: string = '/%' // See % as a combination of / and :
const HideItemShortLexeme: string = '--%' // See % as a combination of / and :
const HideItemVerboseLexeme: string = '/--hide:'
@@ -182,11 +185,26 @@ const MetadataFieldIndicatorLexeme: string = 'with-metadata:'
const CommentPrefix: string = '//'
+const PriorityModifierPrio1Lexeme: string = '/!'
+const PriorityModifierPrio2Lexeme: string = '/!!'
+const PriorityModifierPrio3Lexeme: string = '/!!!'
+
+const PRIO_1: number = 1
+const PRIO_2: number = 2
+const PRIO_3: number = 3
+
+const SortingGroupPriorityPrefixes: { [key: string]: number } = {
+ [PriorityModifierPrio1Lexeme]: PRIO_1,
+ [PriorityModifierPrio2Lexeme]: PRIO_2,
+ [PriorityModifierPrio3Lexeme]: PRIO_3
+}
+
interface SortingGroupType {
filesOnly?: boolean
filenameWithExt?: boolean // The text matching criteria should apply to filename + extension
foldersOnly?: boolean
itemToHide?: boolean
+ priority?: number
}
const SortingGroupPrefixes: { [key: string]: SortingGroupType } = {
@@ -196,6 +214,7 @@ const SortingGroupPrefixes: { [key: string]: SortingGroupType } = {
[FilesWithExtGroupVerboseLexeme]: {filesOnly: true, filenameWithExt: true},
[FoldersGroupShortLexeme]: {foldersOnly: true},
[FoldersGroupVerboseLexeme]: {foldersOnly: true},
+ [AnyTypeGroupLexemeShort]: {},
[AnyTypeGroupLexeme]: {},
[HideItemShortLexeme]: {itemToHide: true},
[HideItemVerboseLexeme]: {itemToHide: true}
@@ -664,23 +683,43 @@ export class SortingSpecProcessor {
}
private parseSortingGroupSpec = (line: string): ParsedSortingGroup | null => {
- const s: string = line.trim()
+ let s: string = line.trim()
if (hasMoreThanOneNumericSortingSymbol(s)) {
this.problem(ProblemCode.TooManyNumericSortingSymbols, 'Maximum one numeric sorting indicator allowed per line')
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
+ for (const priorityPrefix of Object.keys(SortingGroupPriorityPrefixes)) {
+ if (s.startsWith(priorityPrefix + ' ')) {
+ groupPriority = SortingGroupPriorityPrefixes[priorityPrefix]
+ s = s.substring(priorityPrefix.length).trim()
+ break
+ }
+ }
+
const prefixAlone: SortingGroupType = SortingGroupPrefixes[s]
if (prefixAlone) {
if (prefixAlone.itemToHide) {
this.problem(ProblemCode.ItemToHideExactNameWithExtRequired, 'Exact name with ext of file or folders to hide is required')
return null
} else { // !prefixAlone.itemToHide
- return {
- outsidersGroup: true,
- filesOnly: prefixAlone.filesOnly,
- foldersOnly: prefixAlone.foldersOnly
+ if (groupPriority) {
+ this.problem(ProblemCode.PriorityNotAllowedOnOutsidersGroup, 'Priority is not allowed for sorting group with empty match-pattern')
+ return null
+ } else {
+ return {
+ outsidersGroup: true,
+ filesOnly: prefixAlone.filesOnly,
+ foldersOnly: prefixAlone.foldersOnly
+ }
}
}
}
@@ -700,12 +739,26 @@ export class SortingSpecProcessor {
plainSpec: s.substring(prefix.length + 1),
filesOnly: sortingGroupType.filesOnly,
foldersOnly: sortingGroupType.foldersOnly,
- matchFilenameWithExt: sortingGroupType.filenameWithExt
+ matchFilenameWithExt: sortingGroupType.filenameWithExt,
+ priority: groupPriority ?? undefined
}
}
}
}
+ if (groupPriority) {
+ if (s === '') {
+ // 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 {
+ plainSpec: s,
+ priority: groupPriority
+ }
+ }
+ }
return null;
}
@@ -731,8 +784,14 @@ export class SortingSpecProcessor {
if (newGroup) {
if (this.adjustSortingGroupForNumericSortingSymbol(newGroup)) {
if (this.ctx.currentSpec) {
- this.ctx.currentSpec.groups.push(newGroup)
+ const groupIdx = this.ctx.currentSpec.groups.push(newGroup) - 1
this.ctx.currentSpecGroup = newGroup
+ // Consume group with priority
+ if (group.priority && group.priority > 0) {
+ newGroup.priority = group.priority
+ this.addExpediteGroupInfo(this.ctx.currentSpec, group.priority, groupIdx)
+ }
+
return true;
} else {
return false
@@ -794,7 +853,7 @@ export class SortingSpecProcessor {
})
}
- // Populate sorting order for a bit more efficient sorting later on
+ // Populate sorting order down the hierarchy for more clean sorting logic later on
for (let group of spec.groups) {
if (!group.order) {
group.order = spec.defaultOrder ?? DEFAULT_SORT_ORDER
@@ -802,6 +861,18 @@ export class SortingSpecProcessor {
}
}
+ // If any priority sorting group was present in the spec, determine the groups evaluation order
+ if (spec.priorityOrder) {
+ // priorityOrder array already contains at least one priority group, so append all non-priority groups for the final order
+ // (Outsiders groups are ignored intentionally)
+ for (let idx=0; idx < spec.groups.length; idx++) {
+ const group: CustomSortGroup = spec.groups[idx]
+ if (group.priority === undefined && group.type !== CustomSortGroupType.Outsiders) {
+ spec.priorityOrder.push(idx)
+ }
+ }
+ }
+
const CURRENT_FOLDER_PREFIX: string = `${CURRENT_FOLDER_SYMBOL}/`
// Replace the dot-folder names (coming from: 'target-folder: .') with actual folder names
@@ -1108,4 +1179,21 @@ export class SortingSpecProcessor {
}
return true
}
+
+ private addExpediteGroupInfo = (spec: CustomSortSpec, groupPriority: number, groupIdx: number) => {
+ if (!spec.priorityOrder) {
+ spec.priorityOrder = []
+ }
+ let inserted: boolean = false
+ for (let idx=0; idx spec.groups[spec.priorityOrder[idx]].priority!) {
+ spec.priorityOrder.splice(idx, 0, groupIdx)
+ inserted = true
+ break
+ }
+ }
+ if (!inserted) {
+ spec.priorityOrder.push(groupIdx)
+ }
+ }
}